├── .gitattributes ├── .gitignore ├── 3rd ├── CMakeLists.txt ├── HunterGate.cmake ├── scriptarray │ ├── scriptarray.cpp │ └── scriptarray.h ├── scriptbuilder │ ├── scriptbuilder.cpp │ └── scriptbuilder.h └── scriptstdstring │ ├── scriptstdstring.cpp │ ├── scriptstdstring.h │ └── scriptstdstring_utils.cpp ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── doc ├── compat.md ├── performance.md └── status.md ├── include └── asllvm │ ├── config.hpp │ ├── detail │ ├── ashelper.hpp │ ├── asinternalheaders.hpp │ ├── assert.hpp │ ├── builder.hpp │ ├── bytecodeinstruction.hpp │ ├── debuginfo.hpp │ ├── functionbuilder.hpp │ ├── functioncontext.hpp │ ├── fwd.hpp │ ├── jitcompiler.hpp │ ├── llvmglobals.hpp │ ├── modulebuilder.hpp │ ├── modulecommon.hpp │ ├── modulemap.hpp │ ├── runtime.hpp │ ├── stackframe.hpp │ └── vmstate.hpp │ └── jit.hpp ├── src └── asllvm │ ├── detail │ ├── allocationmanager.cpp │ ├── ashelper.cpp │ ├── assert.cpp │ ├── builder.cpp │ ├── debuginfo.cpp │ ├── functionbuilder.cpp │ ├── jitcompiler.cpp │ ├── llvmglobals.cpp │ ├── modulebuilder.cpp │ ├── modulecommon.cpp │ ├── modulemap.cpp │ ├── runtime.cpp │ └── stackframe.cpp │ └── jit.cpp └── tests ├── CMakeLists.txt ├── booleans.cpp ├── branching.cpp ├── classmanip.cpp ├── common.cpp ├── common.hpp ├── enums.cpp ├── floatmath.cpp ├── funcdefs.cpp ├── functions.cpp ├── globals.cpp ├── integermath.cpp ├── main.cpp ├── megatests.cpp ├── recursion.cpp ├── scripts ├── arrays │ ├── initializationlists.as │ ├── simple.as │ └── userclass.as ├── bfint.as ├── devirt.as ├── enums.as ├── fib.as ├── funcdefs.as ├── functions.as ├── globals.as ├── refprimitives.as ├── sharedfuncs.as ├── stringmanip.as ├── switch.as ├── typedefs.as ├── userclasses.as └── vec3f.as └── typedefs.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | /3rd/* linguist-vendored=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cmake/ 2 | build/ 3 | .cache/ 4 | .vscode/ 5 | -------------------------------------------------------------------------------- /3rd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(angelscript-addons STATIC 2 | scriptarray/scriptarray.cpp 3 | scriptbuilder/scriptbuilder.cpp 4 | scriptstdstring/scriptstdstring.cpp 5 | ) 6 | -------------------------------------------------------------------------------- /3rd/HunterGate.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2019, Ruslan Baratov 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | # This is a gate file to Hunter package manager. 26 | # Include this file using `include` command and add package you need, example: 27 | # 28 | # cmake_minimum_required(VERSION 3.2) 29 | # 30 | # include("cmake/HunterGate.cmake") 31 | # HunterGate( 32 | # URL "https://github.com/path/to/hunter/archive.tar.gz" 33 | # SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" 34 | # ) 35 | # 36 | # project(MyProject) 37 | # 38 | # hunter_add_package(Foo) 39 | # hunter_add_package(Boo COMPONENTS Bar Baz) 40 | # 41 | # Projects: 42 | # * https://github.com/hunter-packages/gate/ 43 | # * https://github.com/ruslo/hunter 44 | 45 | option(HUNTER_ENABLED "Enable Hunter package manager support" ON) 46 | 47 | if(HUNTER_ENABLED) 48 | if(CMAKE_VERSION VERSION_LESS "3.2") 49 | message( 50 | FATAL_ERROR 51 | "At least CMake version 3.2 required for Hunter dependency management." 52 | " Update CMake or set HUNTER_ENABLED to OFF." 53 | ) 54 | endif() 55 | endif() 56 | 57 | include(CMakeParseArguments) # cmake_parse_arguments 58 | 59 | option(HUNTER_STATUS_PRINT "Print working status" ON) 60 | option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) 61 | option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON) 62 | 63 | set(HUNTER_ERROR_PAGE "https://docs.hunter.sh/en/latest/reference/errors") 64 | 65 | function(hunter_gate_status_print) 66 | if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) 67 | foreach(print_message ${ARGV}) 68 | message(STATUS "[hunter] ${print_message}") 69 | endforeach() 70 | endif() 71 | endfunction() 72 | 73 | function(hunter_gate_status_debug) 74 | if(HUNTER_STATUS_DEBUG) 75 | foreach(print_message ${ARGV}) 76 | string(TIMESTAMP timestamp) 77 | message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") 78 | endforeach() 79 | endif() 80 | endfunction() 81 | 82 | function(hunter_gate_error_page error_page) 83 | message("------------------------------ ERROR ------------------------------") 84 | message(" ${HUNTER_ERROR_PAGE}/${error_page}.html") 85 | message("-------------------------------------------------------------------") 86 | message("") 87 | message(FATAL_ERROR "") 88 | endfunction() 89 | 90 | function(hunter_gate_internal_error) 91 | message("") 92 | foreach(print_message ${ARGV}) 93 | message("[hunter ** INTERNAL **] ${print_message}") 94 | endforeach() 95 | message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") 96 | message("") 97 | hunter_gate_error_page("error.internal") 98 | endfunction() 99 | 100 | function(hunter_gate_fatal_error) 101 | cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}") 102 | if("${hunter_ERROR_PAGE}" STREQUAL "") 103 | hunter_gate_internal_error("Expected ERROR_PAGE") 104 | endif() 105 | message("") 106 | foreach(x ${hunter_UNPARSED_ARGUMENTS}) 107 | message("[hunter ** FATAL ERROR **] ${x}") 108 | endforeach() 109 | message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") 110 | message("") 111 | hunter_gate_error_page("${hunter_ERROR_PAGE}") 112 | endfunction() 113 | 114 | function(hunter_gate_user_error) 115 | hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data") 116 | endfunction() 117 | 118 | function(hunter_gate_self root version sha1 result) 119 | string(COMPARE EQUAL "${root}" "" is_bad) 120 | if(is_bad) 121 | hunter_gate_internal_error("root is empty") 122 | endif() 123 | 124 | string(COMPARE EQUAL "${version}" "" is_bad) 125 | if(is_bad) 126 | hunter_gate_internal_error("version is empty") 127 | endif() 128 | 129 | string(COMPARE EQUAL "${sha1}" "" is_bad) 130 | if(is_bad) 131 | hunter_gate_internal_error("sha1 is empty") 132 | endif() 133 | 134 | string(SUBSTRING "${sha1}" 0 7 archive_id) 135 | 136 | if(EXISTS "${root}/cmake/Hunter") 137 | set(hunter_self "${root}") 138 | else() 139 | set( 140 | hunter_self 141 | "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" 142 | ) 143 | endif() 144 | 145 | set("${result}" "${hunter_self}" PARENT_SCOPE) 146 | endfunction() 147 | 148 | # Set HUNTER_GATE_ROOT cmake variable to suitable value. 149 | function(hunter_gate_detect_root) 150 | # Check CMake variable 151 | string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) 152 | if(not_empty) 153 | set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) 154 | hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") 155 | return() 156 | endif() 157 | 158 | # Check environment variable 159 | string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) 160 | if(not_empty) 161 | set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) 162 | hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") 163 | return() 164 | endif() 165 | 166 | # Check HOME environment variable 167 | string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) 168 | if(result) 169 | set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) 170 | hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") 171 | return() 172 | endif() 173 | 174 | # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) 175 | if(WIN32) 176 | string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) 177 | if(result) 178 | set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) 179 | hunter_gate_status_debug( 180 | "HUNTER_ROOT set using SYSTEMDRIVE environment variable" 181 | ) 182 | return() 183 | endif() 184 | 185 | string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) 186 | if(result) 187 | set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) 188 | hunter_gate_status_debug( 189 | "HUNTER_ROOT set using USERPROFILE environment variable" 190 | ) 191 | return() 192 | endif() 193 | endif() 194 | 195 | hunter_gate_fatal_error( 196 | "Can't detect HUNTER_ROOT" 197 | ERROR_PAGE "error.detect.hunter.root" 198 | ) 199 | endfunction() 200 | 201 | function(hunter_gate_download dir) 202 | string( 203 | COMPARE 204 | NOTEQUAL 205 | "$ENV{HUNTER_DISABLE_AUTOINSTALL}" 206 | "" 207 | disable_autoinstall 208 | ) 209 | if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) 210 | hunter_gate_fatal_error( 211 | "Hunter not found in '${dir}'" 212 | "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" 213 | "Settings:" 214 | " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" 215 | " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" 216 | ERROR_PAGE "error.run.install" 217 | ) 218 | endif() 219 | string(COMPARE EQUAL "${dir}" "" is_bad) 220 | if(is_bad) 221 | hunter_gate_internal_error("Empty 'dir' argument") 222 | endif() 223 | 224 | string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) 225 | if(is_bad) 226 | hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") 227 | endif() 228 | 229 | string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) 230 | if(is_bad) 231 | hunter_gate_internal_error("HUNTER_GATE_URL empty") 232 | endif() 233 | 234 | set(done_location "${dir}/DONE") 235 | set(sha1_location "${dir}/SHA1") 236 | 237 | set(build_dir "${dir}/Build") 238 | set(cmakelists "${dir}/CMakeLists.txt") 239 | 240 | hunter_gate_status_debug("Locking directory: ${dir}") 241 | file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) 242 | hunter_gate_status_debug("Lock done") 243 | 244 | if(EXISTS "${done_location}") 245 | # while waiting for lock other instance can do all the job 246 | hunter_gate_status_debug("File '${done_location}' found, skip install") 247 | return() 248 | endif() 249 | 250 | file(REMOVE_RECURSE "${build_dir}") 251 | file(REMOVE_RECURSE "${cmakelists}") 252 | 253 | file(MAKE_DIRECTORY "${build_dir}") # check directory permissions 254 | 255 | # Disabling languages speeds up a little bit, reduces noise in the output 256 | # and avoids path too long windows error 257 | file( 258 | WRITE 259 | "${cmakelists}" 260 | "cmake_minimum_required(VERSION 3.2)\n" 261 | "project(HunterDownload LANGUAGES NONE)\n" 262 | "include(ExternalProject)\n" 263 | "ExternalProject_Add(\n" 264 | " Hunter\n" 265 | " URL\n" 266 | " \"${HUNTER_GATE_URL}\"\n" 267 | " URL_HASH\n" 268 | " SHA1=${HUNTER_GATE_SHA1}\n" 269 | " DOWNLOAD_DIR\n" 270 | " \"${dir}\"\n" 271 | " TLS_VERIFY\n" 272 | " ${HUNTER_TLS_VERIFY}\n" 273 | " SOURCE_DIR\n" 274 | " \"${dir}/Unpacked\"\n" 275 | " CONFIGURE_COMMAND\n" 276 | " \"\"\n" 277 | " BUILD_COMMAND\n" 278 | " \"\"\n" 279 | " INSTALL_COMMAND\n" 280 | " \"\"\n" 281 | ")\n" 282 | ) 283 | 284 | if(HUNTER_STATUS_DEBUG) 285 | set(logging_params "") 286 | else() 287 | set(logging_params OUTPUT_QUIET) 288 | endif() 289 | 290 | hunter_gate_status_debug("Run generate") 291 | 292 | # Need to add toolchain file too. 293 | # Otherwise on Visual Studio + MDD this will fail with error: 294 | # "Could not find an appropriate version of the Windows 10 SDK installed on this machine" 295 | if(EXISTS "${CMAKE_TOOLCHAIN_FILE}") 296 | get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE) 297 | set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}") 298 | else() 299 | # 'toolchain_arg' can't be empty 300 | set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=") 301 | endif() 302 | 303 | string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make) 304 | if(no_make) 305 | set(make_arg "") 306 | else() 307 | # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM 308 | set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") 309 | endif() 310 | 311 | execute_process( 312 | COMMAND 313 | "${CMAKE_COMMAND}" 314 | "-H${dir}" 315 | "-B${build_dir}" 316 | "-G${CMAKE_GENERATOR}" 317 | "${toolchain_arg}" 318 | ${make_arg} 319 | WORKING_DIRECTORY "${dir}" 320 | RESULT_VARIABLE download_result 321 | ${logging_params} 322 | ) 323 | 324 | if(NOT download_result EQUAL 0) 325 | hunter_gate_internal_error( 326 | "Configure project failed." 327 | "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}" 328 | "In directory ${dir}" 329 | ) 330 | endif() 331 | 332 | hunter_gate_status_print( 333 | "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" 334 | " ${HUNTER_GATE_URL}" 335 | " -> ${dir}" 336 | ) 337 | execute_process( 338 | COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" 339 | WORKING_DIRECTORY "${dir}" 340 | RESULT_VARIABLE download_result 341 | ${logging_params} 342 | ) 343 | 344 | if(NOT download_result EQUAL 0) 345 | hunter_gate_internal_error("Build project failed") 346 | endif() 347 | 348 | file(REMOVE_RECURSE "${build_dir}") 349 | file(REMOVE_RECURSE "${cmakelists}") 350 | 351 | file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") 352 | file(WRITE "${done_location}" "DONE") 353 | 354 | hunter_gate_status_debug("Finished") 355 | endfunction() 356 | 357 | # Must be a macro so master file 'cmake/Hunter' can 358 | # apply all variables easily just by 'include' command 359 | # (otherwise PARENT_SCOPE magic needed) 360 | macro(HunterGate) 361 | if(HUNTER_GATE_DONE) 362 | # variable HUNTER_GATE_DONE set explicitly for external project 363 | # (see `hunter_download`) 364 | set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) 365 | endif() 366 | 367 | # First HunterGate command will init Hunter, others will be ignored 368 | get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) 369 | 370 | if(NOT HUNTER_ENABLED) 371 | # Empty function to avoid error "unknown function" 372 | function(hunter_add_package) 373 | endfunction() 374 | 375 | set( 376 | _hunter_gate_disabled_mode_dir 377 | "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" 378 | ) 379 | if(EXISTS "${_hunter_gate_disabled_mode_dir}") 380 | hunter_gate_status_debug( 381 | "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" 382 | ) 383 | list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") 384 | endif() 385 | elseif(_hunter_gate_done) 386 | hunter_gate_status_debug("Secondary HunterGate (use old settings)") 387 | hunter_gate_self( 388 | "${HUNTER_CACHED_ROOT}" 389 | "${HUNTER_VERSION}" 390 | "${HUNTER_SHA1}" 391 | _hunter_self 392 | ) 393 | include("${_hunter_self}/cmake/Hunter") 394 | else() 395 | set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}") 396 | 397 | string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) 398 | if(_have_project_name) 399 | hunter_gate_fatal_error( 400 | "Please set HunterGate *before* 'project' command. " 401 | "Detected project: ${PROJECT_NAME}" 402 | ERROR_PAGE "error.huntergate.before.project" 403 | ) 404 | endif() 405 | 406 | cmake_parse_arguments( 407 | HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} 408 | ) 409 | 410 | string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) 411 | string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) 412 | string( 413 | COMPARE 414 | NOTEQUAL 415 | "${HUNTER_GATE_UNPARSED_ARGUMENTS}" 416 | "" 417 | _have_unparsed 418 | ) 419 | string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) 420 | string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) 421 | 422 | if(_have_unparsed) 423 | hunter_gate_user_error( 424 | "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" 425 | ) 426 | endif() 427 | if(_empty_sha1) 428 | hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") 429 | endif() 430 | if(_empty_url) 431 | hunter_gate_user_error("URL suboption of HunterGate is mandatory") 432 | endif() 433 | if(_have_global) 434 | if(HUNTER_GATE_LOCAL) 435 | hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") 436 | endif() 437 | if(_have_filepath) 438 | hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") 439 | endif() 440 | endif() 441 | if(HUNTER_GATE_LOCAL) 442 | if(_have_global) 443 | hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") 444 | endif() 445 | if(_have_filepath) 446 | hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") 447 | endif() 448 | endif() 449 | if(_have_filepath) 450 | if(_have_global) 451 | hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") 452 | endif() 453 | if(HUNTER_GATE_LOCAL) 454 | hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") 455 | endif() 456 | endif() 457 | 458 | hunter_gate_detect_root() # set HUNTER_GATE_ROOT 459 | 460 | # Beautify path, fix probable problems with windows path slashes 461 | get_filename_component( 462 | HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE 463 | ) 464 | hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") 465 | if(NOT HUNTER_ALLOW_SPACES_IN_PATH) 466 | string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) 467 | if(NOT _contain_spaces EQUAL -1) 468 | hunter_gate_fatal_error( 469 | "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." 470 | "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" 471 | "(Use at your own risk!)" 472 | ERROR_PAGE "error.spaces.in.hunter.root" 473 | ) 474 | endif() 475 | endif() 476 | 477 | string( 478 | REGEX 479 | MATCH 480 | "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" 481 | HUNTER_GATE_VERSION 482 | "${HUNTER_GATE_URL}" 483 | ) 484 | string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) 485 | if(_is_empty) 486 | set(HUNTER_GATE_VERSION "unknown") 487 | endif() 488 | 489 | hunter_gate_self( 490 | "${HUNTER_GATE_ROOT}" 491 | "${HUNTER_GATE_VERSION}" 492 | "${HUNTER_GATE_SHA1}" 493 | _hunter_self 494 | ) 495 | 496 | set(_master_location "${_hunter_self}/cmake/Hunter") 497 | if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter") 498 | # Hunter downloaded manually (e.g. by 'git clone') 499 | set(_unused "xxxxxxxxxx") 500 | set(HUNTER_GATE_SHA1 "${_unused}") 501 | set(HUNTER_GATE_VERSION "${_unused}") 502 | else() 503 | get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) 504 | set(_done_location "${_archive_id_location}/DONE") 505 | set(_sha1_location "${_archive_id_location}/SHA1") 506 | 507 | # Check Hunter already downloaded by HunterGate 508 | if(NOT EXISTS "${_done_location}") 509 | hunter_gate_download("${_archive_id_location}") 510 | endif() 511 | 512 | if(NOT EXISTS "${_done_location}") 513 | hunter_gate_internal_error("hunter_gate_download failed") 514 | endif() 515 | 516 | if(NOT EXISTS "${_sha1_location}") 517 | hunter_gate_internal_error("${_sha1_location} not found") 518 | endif() 519 | file(READ "${_sha1_location}" _sha1_value) 520 | string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) 521 | if(NOT _is_equal) 522 | hunter_gate_internal_error( 523 | "Short SHA1 collision:" 524 | " ${_sha1_value} (from ${_sha1_location})" 525 | " ${HUNTER_GATE_SHA1} (HunterGate)" 526 | ) 527 | endif() 528 | if(NOT EXISTS "${_master_location}") 529 | hunter_gate_user_error( 530 | "Master file not found:" 531 | " ${_master_location}" 532 | "try to update Hunter/HunterGate" 533 | ) 534 | endif() 535 | endif() 536 | include("${_master_location}") 537 | set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) 538 | endif() 539 | endmacro() 540 | -------------------------------------------------------------------------------- /3rd/scriptarray/scriptarray.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRIPTARRAY_H 2 | #define SCRIPTARRAY_H 3 | 4 | #ifndef ANGELSCRIPT_H 5 | // Avoid having to inform include path if header is already include before 6 | #include 7 | #endif 8 | 9 | // Sometimes it may be desired to use the same method names as used by C++ STL. 10 | // This may for example reduce time when converting code from script to C++ or 11 | // back. 12 | // 13 | // 0 = off 14 | // 1 = on 15 | #ifndef AS_USE_STLNAMES 16 | #define AS_USE_STLNAMES 0 17 | #endif 18 | 19 | // Some prefer to use property accessors to get/set the length of the array 20 | // This option registers the accessors instead of the method length() 21 | #ifndef AS_USE_ACCESSORS 22 | #define AS_USE_ACCESSORS 0 23 | #endif 24 | 25 | BEGIN_AS_NAMESPACE 26 | 27 | struct SArrayBuffer; 28 | struct SArrayCache; 29 | 30 | class CScriptArray 31 | { 32 | public: 33 | // Set the memory functions that should be used by all CScriptArrays 34 | static void SetMemoryFunctions(asALLOCFUNC_t allocFunc, asFREEFUNC_t freeFunc); 35 | 36 | // Factory functions 37 | static CScriptArray *Create(asITypeInfo *ot); 38 | static CScriptArray *Create(asITypeInfo *ot, asUINT length); 39 | static CScriptArray *Create(asITypeInfo *ot, asUINT length, void *defaultValue); 40 | static CScriptArray *Create(asITypeInfo *ot, void *listBuffer); 41 | 42 | // Memory management 43 | void AddRef() const; 44 | void Release() const; 45 | 46 | // Type information 47 | asITypeInfo *GetArrayObjectType() const; 48 | int GetArrayTypeId() const; 49 | int GetElementTypeId() const; 50 | 51 | // Get the current size 52 | asUINT GetSize() const; 53 | 54 | // Returns true if the array is empty 55 | bool IsEmpty() const; 56 | 57 | // Pre-allocates memory for elements 58 | void Reserve(asUINT maxElements); 59 | 60 | // Resize the array 61 | void Resize(asUINT numElements); 62 | 63 | // Get a pointer to an element. Returns 0 if out of bounds 64 | void *At(asUINT index); 65 | const void *At(asUINT index) const; 66 | 67 | // Set value of an element. 68 | // The value arg should be a pointer to the value that will be copied to the element. 69 | // Remember, if the array holds handles the value parameter should be the 70 | // address of the handle. The refCount of the object will also be incremented 71 | void SetValue(asUINT index, void *value); 72 | 73 | // Copy the contents of one array to another (only if the types are the same) 74 | CScriptArray &operator=(const CScriptArray&); 75 | 76 | // Compare two arrays 77 | bool operator==(const CScriptArray &) const; 78 | 79 | // Array manipulation 80 | void InsertAt(asUINT index, void *value); 81 | void InsertAt(asUINT index, const CScriptArray &arr); 82 | void InsertLast(void *value); 83 | void RemoveAt(asUINT index); 84 | void RemoveLast(); 85 | void RemoveRange(asUINT start, asUINT count); 86 | void SortAsc(); 87 | void SortDesc(); 88 | void SortAsc(asUINT startAt, asUINT count); 89 | void SortDesc(asUINT startAt, asUINT count); 90 | void Sort(asUINT startAt, asUINT count, bool asc); 91 | void Sort(asIScriptFunction *less, asUINT startAt, asUINT count); 92 | void Reverse(); 93 | int Find(void *value) const; 94 | int Find(asUINT startAt, void *value) const; 95 | int FindByRef(void *ref) const; 96 | int FindByRef(asUINT startAt, void *ref) const; 97 | 98 | // Return the address of internal buffer for direct manipulation of elements 99 | void *GetBuffer(); 100 | 101 | // GC methods 102 | int GetRefCount(); 103 | void SetFlag(); 104 | bool GetFlag(); 105 | void EnumReferences(asIScriptEngine *engine); 106 | void ReleaseAllHandles(asIScriptEngine *engine); 107 | 108 | protected: 109 | mutable int refCount; 110 | mutable bool gcFlag; 111 | asITypeInfo *objType; 112 | SArrayBuffer *buffer; 113 | int elementSize; 114 | int subTypeId; 115 | 116 | // Constructors 117 | CScriptArray(asITypeInfo *ot, void *initBuf); // Called from script when initialized with list 118 | CScriptArray(asUINT length, asITypeInfo *ot); 119 | CScriptArray(asUINT length, void *defVal, asITypeInfo *ot); 120 | CScriptArray(const CScriptArray &other); 121 | virtual ~CScriptArray(); 122 | 123 | bool Less(const void *a, const void *b, bool asc); 124 | void *GetArrayItemPointer(int index); 125 | void *GetDataPointer(void *buffer); 126 | void Copy(void *dst, void *src); 127 | void Precache(); 128 | bool CheckMaxSize(asUINT numElements); 129 | void Resize(int delta, asUINT at); 130 | void CreateBuffer(SArrayBuffer **buf, asUINT numElements); 131 | void DeleteBuffer(SArrayBuffer *buf); 132 | void CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src); 133 | void Construct(SArrayBuffer *buf, asUINT start, asUINT end); 134 | void Destruct(SArrayBuffer *buf, asUINT start, asUINT end); 135 | bool Equals(const void *a, const void *b, asIScriptContext *ctx, SArrayCache *cache) const; 136 | }; 137 | 138 | void RegisterScriptArray(asIScriptEngine *engine, bool defaultArray); 139 | 140 | END_AS_NAMESPACE 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /3rd/scriptbuilder/scriptbuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "scriptbuilder.h" 2 | #include 3 | #include 4 | using namespace std; 5 | 6 | #include 7 | #if defined(_MSC_VER) && !defined(_WIN32_WCE) && !defined(__S3E__) 8 | #include 9 | #endif 10 | #ifdef _WIN32_WCE 11 | #include // For GetModuleFileName() 12 | #endif 13 | 14 | #if defined(__S3E__) || defined(__APPLE__) || defined(__GNUC__) 15 | #include // For getcwd() 16 | #endif 17 | 18 | BEGIN_AS_NAMESPACE 19 | 20 | // Helper functions 21 | static string GetCurrentDir(); 22 | static string GetAbsolutePath(const string &path); 23 | 24 | 25 | CScriptBuilder::CScriptBuilder() 26 | { 27 | engine = 0; 28 | module = 0; 29 | 30 | includeCallback = 0; 31 | includeParam = 0; 32 | 33 | pragmaCallback = 0; 34 | pragmaParam = 0; 35 | } 36 | 37 | void CScriptBuilder::SetIncludeCallback(INCLUDECALLBACK_t callback, void *userParam) 38 | { 39 | includeCallback = callback; 40 | includeParam = userParam; 41 | } 42 | 43 | void CScriptBuilder::SetPragmaCallback(PRAGMACALLBACK_t callback, void *userParam) 44 | { 45 | pragmaCallback = callback; 46 | pragmaParam = userParam; 47 | } 48 | 49 | int CScriptBuilder::StartNewModule(asIScriptEngine *inEngine, const char *moduleName) 50 | { 51 | if(inEngine == 0 ) return -1; 52 | 53 | engine = inEngine; 54 | module = inEngine->GetModule(moduleName, asGM_ALWAYS_CREATE); 55 | if( module == 0 ) 56 | return -1; 57 | 58 | ClearAll(); 59 | 60 | return 0; 61 | } 62 | 63 | asIScriptEngine *CScriptBuilder::GetEngine() 64 | { 65 | return engine; 66 | } 67 | 68 | asIScriptModule *CScriptBuilder::GetModule() 69 | { 70 | return module; 71 | } 72 | 73 | unsigned int CScriptBuilder::GetSectionCount() const 74 | { 75 | return (unsigned int)(includedScripts.size()); 76 | } 77 | 78 | string CScriptBuilder::GetSectionName(unsigned int idx) const 79 | { 80 | if( idx >= includedScripts.size() ) return ""; 81 | 82 | #ifdef _WIN32 83 | set::const_iterator it = includedScripts.begin(); 84 | #else 85 | set::const_iterator it = includedScripts.begin(); 86 | #endif 87 | while( idx-- > 0 ) it++; 88 | return *it; 89 | } 90 | 91 | // Returns 1 if the section was included 92 | // Returns 0 if the section was not included because it had already been included before 93 | // Returns <0 if there was an error 94 | int CScriptBuilder::AddSectionFromFile(const char *filename) 95 | { 96 | // The file name stored in the set should be the fully resolved name because 97 | // it is possible to name the same file in multiple ways using relative paths. 98 | string fullpath = GetAbsolutePath(filename); 99 | 100 | if( IncludeIfNotAlreadyIncluded(fullpath.c_str()) ) 101 | { 102 | int r = LoadScriptSection(fullpath.c_str()); 103 | if( r < 0 ) 104 | return r; 105 | else 106 | return 1; 107 | } 108 | 109 | return 0; 110 | } 111 | 112 | // Returns 1 if the section was included 113 | // Returns 0 if the section was not included because it had already been included before 114 | // Returns <0 if there was an error 115 | int CScriptBuilder::AddSectionFromMemory(const char *sectionName, const char *scriptCode, unsigned int scriptLength, int lineOffset) 116 | { 117 | if( IncludeIfNotAlreadyIncluded(sectionName) ) 118 | { 119 | int r = ProcessScriptSection(scriptCode, scriptLength, sectionName, lineOffset); 120 | if( r < 0 ) 121 | return r; 122 | else 123 | return 1; 124 | } 125 | 126 | return 0; 127 | } 128 | 129 | int CScriptBuilder::BuildModule() 130 | { 131 | return Build(); 132 | } 133 | 134 | void CScriptBuilder::DefineWord(const char *word) 135 | { 136 | string sword = word; 137 | if( definedWords.find(sword) == definedWords.end() ) 138 | { 139 | definedWords.insert(sword); 140 | } 141 | } 142 | 143 | void CScriptBuilder::ClearAll() 144 | { 145 | includedScripts.clear(); 146 | 147 | #if AS_PROCESS_METADATA == 1 148 | currentClass = ""; 149 | currentNamespace = ""; 150 | 151 | foundDeclarations.clear(); 152 | typeMetadataMap.clear(); 153 | funcMetadataMap.clear(); 154 | varMetadataMap.clear(); 155 | #endif 156 | } 157 | 158 | bool CScriptBuilder::IncludeIfNotAlreadyIncluded(const char *filename) 159 | { 160 | string scriptFile = filename; 161 | if( includedScripts.find(scriptFile) != includedScripts.end() ) 162 | { 163 | // Already included 164 | return false; 165 | } 166 | 167 | // Add the file to the set of included sections 168 | includedScripts.insert(scriptFile); 169 | 170 | return true; 171 | } 172 | 173 | int CScriptBuilder::LoadScriptSection(const char *filename) 174 | { 175 | // Open the script file 176 | string scriptFile = filename; 177 | #if _MSC_VER >= 1500 && !defined(__S3E__) 178 | FILE *f = 0; 179 | fopen_s(&f, scriptFile.c_str(), "rb"); 180 | #else 181 | FILE *f = fopen(scriptFile.c_str(), "rb"); 182 | #endif 183 | if( f == 0 ) 184 | { 185 | // Write a message to the engine's message callback 186 | string msg = "Failed to open script file '" + GetAbsolutePath(scriptFile) + "'"; 187 | engine->WriteMessage(filename, 0, 0, asMSGTYPE_ERROR, msg.c_str()); 188 | 189 | // TODO: Write the file where this one was included from 190 | 191 | return -1; 192 | } 193 | 194 | // Determine size of the file 195 | fseek(f, 0, SEEK_END); 196 | int len = ftell(f); 197 | fseek(f, 0, SEEK_SET); 198 | 199 | // On Win32 it is possible to do the following instead 200 | // int len = _filelength(_fileno(f)); 201 | 202 | // Read the entire file 203 | string code; 204 | size_t c = 0; 205 | if( len > 0 ) 206 | { 207 | code.resize(len); 208 | c = fread(&code[0], len, 1, f); 209 | } 210 | 211 | fclose(f); 212 | 213 | if( c == 0 && len > 0 ) 214 | { 215 | // Write a message to the engine's message callback 216 | string msg = "Failed to load script file '" + GetAbsolutePath(scriptFile) + "'"; 217 | engine->WriteMessage(filename, 0, 0, asMSGTYPE_ERROR, msg.c_str()); 218 | return -1; 219 | } 220 | 221 | // Process the script section even if it is zero length so that the name is registered 222 | return ProcessScriptSection(code.c_str(), (unsigned int)(code.length()), filename, 0); 223 | } 224 | 225 | int CScriptBuilder::ProcessScriptSection(const char *script, unsigned int length, const char *sectionname, int lineOffset) 226 | { 227 | vector includes; 228 | 229 | // Perform a superficial parsing of the script first to store the metadata 230 | if( length ) 231 | modifiedScript.assign(script, length); 232 | else 233 | modifiedScript = script; 234 | 235 | // First perform the checks for #if directives to exclude code that shouldn't be compiled 236 | unsigned int pos = 0; 237 | int nested = 0; 238 | while( pos < modifiedScript.size() ) 239 | { 240 | asUINT len = 0; 241 | asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 242 | if( t == asTC_UNKNOWN && modifiedScript[pos] == '#' && (pos + 1 < modifiedScript.size()) ) 243 | { 244 | int start = pos++; 245 | 246 | // Is this an #if directive? 247 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 248 | 249 | string token; 250 | token.assign(&modifiedScript[pos], len); 251 | 252 | pos += len; 253 | 254 | if( token == "if" ) 255 | { 256 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 257 | if( t == asTC_WHITESPACE ) 258 | { 259 | pos += len; 260 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 261 | } 262 | 263 | if( t == asTC_IDENTIFIER ) 264 | { 265 | string word; 266 | word.assign(&modifiedScript[pos], len); 267 | 268 | // Overwrite the #if directive with space characters to avoid compiler error 269 | pos += len; 270 | OverwriteCode(start, pos-start); 271 | 272 | // Has this identifier been defined by the application or not? 273 | if( definedWords.find(word) == definedWords.end() ) 274 | { 275 | // Exclude all the code until and including the #endif 276 | pos = ExcludeCode(pos); 277 | } 278 | else 279 | { 280 | nested++; 281 | } 282 | } 283 | } 284 | else if( token == "endif" ) 285 | { 286 | // Only remove the #endif if there was a matching #if 287 | if( nested > 0 ) 288 | { 289 | OverwriteCode(start, pos-start); 290 | nested--; 291 | } 292 | } 293 | } 294 | else 295 | pos += len; 296 | } 297 | 298 | #if AS_PROCESS_METADATA == 1 299 | // Preallocate memory 300 | string name, declaration; 301 | vector metadata; 302 | declaration.reserve(100); 303 | #endif 304 | 305 | // Then check for meta data and #include directives 306 | pos = 0; 307 | while( pos < modifiedScript.size() ) 308 | { 309 | asUINT len = 0; 310 | asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 311 | if( t == asTC_COMMENT || t == asTC_WHITESPACE ) 312 | { 313 | pos += len; 314 | continue; 315 | } 316 | 317 | #if AS_PROCESS_METADATA == 1 318 | // Check if class 319 | if( currentClass == "" && modifiedScript.substr(pos,len) == "class" ) 320 | { 321 | // Get the identifier after "class" 322 | do 323 | { 324 | pos += len; 325 | if( pos >= modifiedScript.size() ) 326 | { 327 | t = asTC_UNKNOWN; 328 | break; 329 | } 330 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 331 | } while(t == asTC_COMMENT || t == asTC_WHITESPACE); 332 | 333 | if( t == asTC_IDENTIFIER ) 334 | { 335 | currentClass = modifiedScript.substr(pos,len); 336 | 337 | // Search until first { or ; is encountered 338 | while( pos < modifiedScript.length() ) 339 | { 340 | engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 341 | 342 | // If start of class section encountered stop 343 | if( modifiedScript[pos] == '{' ) 344 | { 345 | pos += len; 346 | break; 347 | } 348 | else if (modifiedScript[pos] == ';') 349 | { 350 | // The class declaration has ended and there are no children 351 | currentClass = ""; 352 | pos += len; 353 | break; 354 | } 355 | 356 | // Check next symbol 357 | pos += len; 358 | } 359 | } 360 | 361 | continue; 362 | } 363 | 364 | // Check if end of class 365 | if( currentClass != "" && modifiedScript[pos] == '}' ) 366 | { 367 | currentClass = ""; 368 | pos += len; 369 | continue; 370 | } 371 | 372 | // Check if namespace 373 | if( modifiedScript.substr(pos,len) == "namespace" ) 374 | { 375 | // Get the identifier after "namespace" 376 | do 377 | { 378 | pos += len; 379 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 380 | } while(t == asTC_COMMENT || t == asTC_WHITESPACE); 381 | 382 | if( currentNamespace != "" ) 383 | currentNamespace += "::"; 384 | currentNamespace += modifiedScript.substr(pos,len); 385 | 386 | // Search until first { is encountered 387 | while( pos < modifiedScript.length() ) 388 | { 389 | engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 390 | 391 | // If start of namespace section encountered stop 392 | if( modifiedScript[pos] == '{' ) 393 | { 394 | pos += len; 395 | break; 396 | } 397 | 398 | // Check next symbol 399 | pos += len; 400 | } 401 | 402 | continue; 403 | } 404 | 405 | // Check if end of namespace 406 | if( currentNamespace != "" && modifiedScript[pos] == '}' ) 407 | { 408 | size_t found = currentNamespace.rfind( "::" ); 409 | if( found != string::npos ) 410 | { 411 | currentNamespace.erase( found ); 412 | } 413 | else 414 | { 415 | currentNamespace = ""; 416 | } 417 | pos += len; 418 | continue; 419 | } 420 | 421 | // Is this the start of metadata? 422 | if( modifiedScript[pos] == '[' ) 423 | { 424 | // Get the metadata string 425 | pos = ExtractMetadata(pos, metadata); 426 | 427 | // Determine what this metadata is for 428 | int type; 429 | ExtractDeclaration(pos, name, declaration, type); 430 | 431 | // Store away the declaration in a map for lookup after the build has completed 432 | if( type > 0 ) 433 | { 434 | SMetadataDecl decl(metadata, name, declaration, type, currentClass, currentNamespace); 435 | foundDeclarations.push_back(decl); 436 | } 437 | } 438 | else 439 | #endif 440 | // Is this a preprocessor directive? 441 | if( modifiedScript[pos] == '#' && (pos + 1 < modifiedScript.size()) ) 442 | { 443 | int start = pos++; 444 | 445 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 446 | if( t == asTC_IDENTIFIER ) 447 | { 448 | string token; 449 | token.assign(&modifiedScript[pos], len); 450 | if( token == "include" ) 451 | { 452 | pos += len; 453 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 454 | if( t == asTC_WHITESPACE ) 455 | { 456 | pos += len; 457 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 458 | } 459 | 460 | if( t == asTC_VALUE && len > 2 && (modifiedScript[pos] == '"' || modifiedScript[pos] == '\'') ) 461 | { 462 | // Get the include file 463 | string includefile; 464 | includefile.assign(&modifiedScript[pos+1], len-2); 465 | pos += len; 466 | 467 | // Store it for later processing 468 | includes.push_back(includefile); 469 | 470 | // Overwrite the include directive with space characters to avoid compiler error 471 | OverwriteCode(start, pos-start); 472 | } 473 | } 474 | else if (token == "pragma") 475 | { 476 | // Read until the end of the line 477 | pos += len; 478 | for (; pos < modifiedScript.size() && modifiedScript[pos] != '\n'; pos++); 479 | 480 | // Call the pragma callback 481 | string pragmaText(&modifiedScript[start + 7], pos - start - 7); 482 | int r = pragmaCallback ? pragmaCallback(pragmaText, *this, pragmaParam) : -1; 483 | if (r < 0) 484 | { 485 | // TODO: Report the correct line number 486 | engine->WriteMessage(sectionname, 0, 0, asMSGTYPE_ERROR, "Invalid #pragma directive"); 487 | return r; 488 | } 489 | 490 | // Overwrite the pragma directive with space characters to avoid compiler error 491 | OverwriteCode(start, pos - start); 492 | } 493 | } 494 | } 495 | // Don't search for metadata/includes within statement blocks or between tokens in statements 496 | else 497 | { 498 | pos = SkipStatement(pos); 499 | } 500 | } 501 | 502 | // Build the actual script 503 | engine->SetEngineProperty(asEP_COPY_SCRIPT_SECTIONS, true); 504 | module->AddScriptSection(sectionname, modifiedScript.c_str(), modifiedScript.size(), lineOffset); 505 | 506 | if( includes.size() > 0 ) 507 | { 508 | // If the callback has been set, then call it for each included file 509 | if( includeCallback ) 510 | { 511 | for( int n = 0; n < (int)includes.size(); n++ ) 512 | { 513 | int r = includeCallback(includes[n].c_str(), sectionname, this, includeParam); 514 | if( r < 0 ) 515 | return r; 516 | } 517 | } 518 | else 519 | { 520 | // By default we try to load the included file from the relative directory of the current file 521 | 522 | // Determine the path of the current script so that we can resolve relative paths for includes 523 | string path = sectionname; 524 | size_t posOfSlash = path.find_last_of("/\\"); 525 | if( posOfSlash != string::npos ) 526 | path.resize(posOfSlash+1); 527 | else 528 | path = ""; 529 | 530 | // Load the included scripts 531 | for( int n = 0; n < (int)includes.size(); n++ ) 532 | { 533 | // If the include is a relative path, then prepend the path of the originating script 534 | if( includes[n].find_first_of("/\\") != 0 && 535 | includes[n].find_first_of(":") == string::npos ) 536 | { 537 | includes[n] = path + includes[n]; 538 | } 539 | 540 | // Include the script section 541 | int r = AddSectionFromFile(includes[n].c_str()); 542 | if( r < 0 ) 543 | return r; 544 | } 545 | } 546 | } 547 | 548 | return 0; 549 | } 550 | 551 | int CScriptBuilder::Build() 552 | { 553 | int r = module->Build(); 554 | if( r < 0 ) 555 | return r; 556 | 557 | #if AS_PROCESS_METADATA == 1 558 | // After the script has been built, the metadata strings should be 559 | // stored for later lookup by function id, type id, and variable index 560 | for( int n = 0; n < (int)foundDeclarations.size(); n++ ) 561 | { 562 | SMetadataDecl *decl = &foundDeclarations[n]; 563 | module->SetDefaultNamespace(decl->nameSpace.c_str()); 564 | if( decl->type == MDT_TYPE ) 565 | { 566 | // Find the type id 567 | int typeId = module->GetTypeIdByDecl(decl->declaration.c_str()); 568 | assert( typeId >= 0 ); 569 | if( typeId >= 0 ) 570 | typeMetadataMap.insert(map >::value_type(typeId, decl->metadata)); 571 | } 572 | else if( decl->type == MDT_FUNC ) 573 | { 574 | if( decl->parentClass == "" ) 575 | { 576 | // Find the function id 577 | asIScriptFunction *func = module->GetFunctionByDecl(decl->declaration.c_str()); 578 | assert( func ); 579 | if( func ) 580 | funcMetadataMap.insert(map >::value_type(func->GetId(), decl->metadata)); 581 | } 582 | else 583 | { 584 | // Find the method id 585 | int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str()); 586 | assert( typeId > 0 ); 587 | map::iterator it = classMetadataMap.find(typeId); 588 | if( it == classMetadataMap.end() ) 589 | { 590 | classMetadataMap.insert(map::value_type(typeId, SClassMetadata(decl->parentClass))); 591 | it = classMetadataMap.find(typeId); 592 | } 593 | 594 | asITypeInfo *type = engine->GetTypeInfoById(typeId); 595 | asIScriptFunction *func = type->GetMethodByDecl(decl->declaration.c_str()); 596 | assert( func ); 597 | if( func ) 598 | it->second.funcMetadataMap.insert(map >::value_type(func->GetId(), decl->metadata)); 599 | } 600 | } 601 | else if( decl->type == MDT_VIRTPROP ) 602 | { 603 | if( decl->parentClass == "" ) 604 | { 605 | // Find the global virtual property accessors 606 | asIScriptFunction *func = module->GetFunctionByName(("get_" + decl->declaration).c_str()); 607 | if( func ) 608 | funcMetadataMap.insert(map >::value_type(func->GetId(), decl->metadata)); 609 | func = module->GetFunctionByName(("set_" + decl->declaration).c_str()); 610 | if( func ) 611 | funcMetadataMap.insert(map >::value_type(func->GetId(), decl->metadata)); 612 | } 613 | else 614 | { 615 | // Find the method virtual property accessors 616 | int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str()); 617 | assert( typeId > 0 ); 618 | map::iterator it = classMetadataMap.find(typeId); 619 | if( it == classMetadataMap.end() ) 620 | { 621 | classMetadataMap.insert(map::value_type(typeId, SClassMetadata(decl->parentClass))); 622 | it = classMetadataMap.find(typeId); 623 | } 624 | 625 | asITypeInfo *type = engine->GetTypeInfoById(typeId); 626 | asIScriptFunction *func = type->GetMethodByName(("get_" + decl->declaration).c_str()); 627 | if( func ) 628 | it->second.funcMetadataMap.insert(map >::value_type(func->GetId(), decl->metadata)); 629 | func = type->GetMethodByName(("set_" + decl->declaration).c_str()); 630 | if( func ) 631 | it->second.funcMetadataMap.insert(map >::value_type(func->GetId(), decl->metadata)); 632 | } 633 | } 634 | else if( decl->type == MDT_VAR ) 635 | { 636 | if( decl->parentClass == "" ) 637 | { 638 | // Find the global variable index 639 | int varIdx = module->GetGlobalVarIndexByName(decl->declaration.c_str()); 640 | assert( varIdx >= 0 ); 641 | if( varIdx >= 0 ) 642 | varMetadataMap.insert(map >::value_type(varIdx, decl->metadata)); 643 | } 644 | else 645 | { 646 | int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str()); 647 | assert( typeId > 0 ); 648 | 649 | // Add the classes if needed 650 | map::iterator it = classMetadataMap.find(typeId); 651 | if( it == classMetadataMap.end() ) 652 | { 653 | classMetadataMap.insert(map::value_type(typeId, SClassMetadata(decl->parentClass))); 654 | it = classMetadataMap.find(typeId); 655 | } 656 | 657 | // Add the variable to class 658 | asITypeInfo *objectType = engine->GetTypeInfoById(typeId); 659 | int idx = -1; 660 | 661 | // Search through all properties to get proper declaration 662 | for( asUINT i = 0; i < (asUINT)objectType->GetPropertyCount(); ++i ) 663 | { 664 | const char *name; 665 | objectType->GetProperty(i, &name); 666 | if( decl->declaration == name ) 667 | { 668 | idx = i; 669 | break; 670 | } 671 | } 672 | 673 | // If found, add it 674 | assert( idx >= 0 ); 675 | if( idx >= 0 ) it->second.varMetadataMap.insert(map >::value_type(idx, decl->metadata)); 676 | } 677 | } 678 | else if (decl->type == MDT_FUNC_OR_VAR) 679 | { 680 | if (decl->parentClass == "") 681 | { 682 | // Find the global variable index 683 | int varIdx = module->GetGlobalVarIndexByName(decl->name.c_str()); 684 | if (varIdx >= 0) 685 | varMetadataMap.insert(map >::value_type(varIdx, decl->metadata)); 686 | else 687 | { 688 | asIScriptFunction *func = module->GetFunctionByDecl(decl->declaration.c_str()); 689 | assert(func); 690 | if (func) 691 | funcMetadataMap.insert(map >::value_type(func->GetId(), decl->metadata)); 692 | } 693 | } 694 | else 695 | { 696 | int typeId = module->GetTypeIdByDecl(decl->parentClass.c_str()); 697 | assert(typeId > 0); 698 | 699 | // Add the classes if needed 700 | map::iterator it = classMetadataMap.find(typeId); 701 | if (it == classMetadataMap.end()) 702 | { 703 | classMetadataMap.insert(map::value_type(typeId, SClassMetadata(decl->parentClass))); 704 | it = classMetadataMap.find(typeId); 705 | } 706 | 707 | // Add the variable to class 708 | asITypeInfo *objectType = engine->GetTypeInfoById(typeId); 709 | int idx = -1; 710 | 711 | // Search through all properties to get proper declaration 712 | for (asUINT i = 0; i < (asUINT)objectType->GetPropertyCount(); ++i) 713 | { 714 | const char *name; 715 | objectType->GetProperty(i, &name); 716 | if (decl->name == name) 717 | { 718 | idx = i; 719 | break; 720 | } 721 | } 722 | 723 | // If found, add it 724 | if (idx >= 0) 725 | it->second.varMetadataMap.insert(map >::value_type(idx, decl->metadata)); 726 | else 727 | { 728 | // Look for the matching method instead 729 | asITypeInfo *type = engine->GetTypeInfoById(typeId); 730 | asIScriptFunction *func = type->GetMethodByDecl(decl->declaration.c_str()); 731 | assert(func); 732 | if (func) 733 | it->second.funcMetadataMap.insert(map >::value_type(func->GetId(), decl->metadata)); 734 | } 735 | } 736 | } 737 | } 738 | module->SetDefaultNamespace(""); 739 | #endif 740 | 741 | return 0; 742 | } 743 | 744 | int CScriptBuilder::SkipStatement(int pos) 745 | { 746 | asUINT len = 0; 747 | 748 | // Skip until ; or { whichever comes first 749 | while( pos < (int)modifiedScript.length() && modifiedScript[pos] != ';' && modifiedScript[pos] != '{' ) 750 | { 751 | engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 752 | pos += len; 753 | } 754 | 755 | // Skip entire statement block 756 | if( pos < (int)modifiedScript.length() && modifiedScript[pos] == '{' ) 757 | { 758 | pos += 1; 759 | 760 | // Find the end of the statement block 761 | int level = 1; 762 | while( level > 0 && pos < (int)modifiedScript.size() ) 763 | { 764 | asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 765 | if( t == asTC_KEYWORD ) 766 | { 767 | if( modifiedScript[pos] == '{' ) 768 | level++; 769 | else if( modifiedScript[pos] == '}' ) 770 | level--; 771 | } 772 | 773 | pos += len; 774 | } 775 | } 776 | else 777 | pos += 1; 778 | 779 | return pos; 780 | } 781 | 782 | // Overwrite all code with blanks until the matching #endif 783 | int CScriptBuilder::ExcludeCode(int pos) 784 | { 785 | asUINT len = 0; 786 | int nested = 0; 787 | while( pos < (int)modifiedScript.size() ) 788 | { 789 | engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 790 | if( modifiedScript[pos] == '#' ) 791 | { 792 | modifiedScript[pos] = ' '; 793 | pos++; 794 | 795 | // Is it an #if or #endif directive? 796 | engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 797 | string token; 798 | token.assign(&modifiedScript[pos], len); 799 | OverwriteCode(pos, len); 800 | 801 | if( token == "if" ) 802 | { 803 | nested++; 804 | } 805 | else if( token == "endif" ) 806 | { 807 | if( nested-- == 0 ) 808 | { 809 | pos += len; 810 | break; 811 | } 812 | } 813 | } 814 | else if( modifiedScript[pos] != '\n' ) 815 | { 816 | OverwriteCode(pos, len); 817 | } 818 | pos += len; 819 | } 820 | 821 | return pos; 822 | } 823 | 824 | // Overwrite all characters except line breaks with blanks 825 | void CScriptBuilder::OverwriteCode(int start, int len) 826 | { 827 | char *code = &modifiedScript[start]; 828 | for( int n = 0; n < len; n++ ) 829 | { 830 | if( *code != '\n' ) 831 | *code = ' '; 832 | code++; 833 | } 834 | } 835 | 836 | #if AS_PROCESS_METADATA == 1 837 | int CScriptBuilder::ExtractMetadata(int pos, vector &metadata) 838 | { 839 | metadata.clear(); 840 | 841 | // Extract all metadata. They can be separated by whitespace and comments 842 | for (;;) 843 | { 844 | string metadataString = ""; 845 | 846 | // Overwrite the metadata with space characters to allow compilation 847 | modifiedScript[pos] = ' '; 848 | 849 | // Skip opening brackets 850 | pos += 1; 851 | 852 | int level = 1; 853 | asUINT len = 0; 854 | while (level > 0 && pos < (int)modifiedScript.size()) 855 | { 856 | asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 857 | if (t == asTC_KEYWORD) 858 | { 859 | if (modifiedScript[pos] == '[') 860 | level++; 861 | else if (modifiedScript[pos] == ']') 862 | level--; 863 | } 864 | 865 | // Copy the metadata to our buffer 866 | if (level > 0) 867 | metadataString.append(&modifiedScript[pos], len); 868 | 869 | // Overwrite the metadata with space characters to allow compilation 870 | if (t != asTC_WHITESPACE) 871 | OverwriteCode(pos, len); 872 | 873 | pos += len; 874 | } 875 | 876 | metadata.push_back(metadataString); 877 | 878 | // Check for more metadata. Possibly separated by comments 879 | asETokenClass t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 880 | while (t == asTC_COMMENT || t == asTC_WHITESPACE) 881 | { 882 | pos += len; 883 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 884 | } 885 | 886 | if (modifiedScript[pos] != '[') 887 | break; 888 | } 889 | 890 | return pos; 891 | } 892 | 893 | int CScriptBuilder::ExtractDeclaration(int pos, string &name, string &declaration, int &type) 894 | { 895 | declaration = ""; 896 | type = 0; 897 | 898 | int start = pos; 899 | 900 | std::string token; 901 | asUINT len = 0; 902 | asETokenClass t = asTC_WHITESPACE; 903 | 904 | // Skip white spaces, comments, and leading decorators 905 | do 906 | { 907 | pos += len; 908 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 909 | token.assign(&modifiedScript[pos], len); 910 | } while ( t == asTC_WHITESPACE || t == asTC_COMMENT || 911 | token == "private" || token == "protected" || 912 | token == "shared" || token == "external" || 913 | token == "final" || token == "abstract" ); 914 | 915 | // We're expecting, either a class, interface, function, or variable declaration 916 | if( t == asTC_KEYWORD || t == asTC_IDENTIFIER ) 917 | { 918 | token.assign(&modifiedScript[pos], len); 919 | if( token == "interface" || token == "class" || token == "enum" ) 920 | { 921 | // Skip white spaces and comments 922 | do 923 | { 924 | pos += len; 925 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 926 | } while ( t == asTC_WHITESPACE || t == asTC_COMMENT ); 927 | 928 | if( t == asTC_IDENTIFIER ) 929 | { 930 | type = MDT_TYPE; 931 | declaration.assign(&modifiedScript[pos], len); 932 | pos += len; 933 | return pos; 934 | } 935 | } 936 | else 937 | { 938 | // For function declarations, store everything up to the start of the 939 | // statement block, except for succeeding decorators (final, override, etc) 940 | 941 | // For variable declaration store just the name as there can only be one 942 | 943 | // We'll only know if the declaration is a variable or function declaration 944 | // when we see the statement block, or absense of a statement block. 945 | bool hasParenthesis = false; 946 | int nestedParenthesis = 0; 947 | declaration.append(&modifiedScript[pos], len); 948 | pos += len; 949 | for(; pos < (int)modifiedScript.size();) 950 | { 951 | t = engine->ParseToken(&modifiedScript[pos], modifiedScript.size() - pos, &len); 952 | token.assign(&modifiedScript[pos], len); 953 | if (t == asTC_KEYWORD) 954 | { 955 | if (token == "{" && nestedParenthesis == 0) 956 | { 957 | if (hasParenthesis) 958 | { 959 | // We've found the end of a function signature 960 | type = MDT_FUNC; 961 | } 962 | else 963 | { 964 | // We've found a virtual property. Just keep the name 965 | declaration = name; 966 | type = MDT_VIRTPROP; 967 | } 968 | return pos; 969 | } 970 | if ((token == "=" && !hasParenthesis) || token == ";") 971 | { 972 | if (hasParenthesis) 973 | { 974 | // The declaration is ambigous. It can be a variable with initialization, or a function prototype 975 | type = MDT_FUNC_OR_VAR; 976 | } 977 | else 978 | { 979 | // Substitute the declaration with just the name 980 | declaration = name; 981 | type = MDT_VAR; 982 | } 983 | return pos; 984 | } 985 | else if (token == "(") 986 | { 987 | nestedParenthesis++; 988 | 989 | // This is the first parenthesis we encounter. If the parenthesis isn't followed 990 | // by a statement block, then this is a variable declaration, in which case we 991 | // should only store the type and name of the variable, not the initialization parameters. 992 | hasParenthesis = true; 993 | } 994 | else if (token == ")") 995 | { 996 | nestedParenthesis--; 997 | } 998 | } 999 | else if( t == asTC_IDENTIFIER ) 1000 | { 1001 | name = token; 1002 | } 1003 | 1004 | // Skip trailing decorators 1005 | if( !hasParenthesis || nestedParenthesis > 0 || t != asTC_IDENTIFIER || (token != "final" && token != "override") ) 1006 | declaration += token; 1007 | 1008 | pos += len; 1009 | } 1010 | } 1011 | } 1012 | 1013 | return start; 1014 | } 1015 | 1016 | vector CScriptBuilder::GetMetadataForType(int typeId) 1017 | { 1018 | map >::iterator it = typeMetadataMap.find(typeId); 1019 | if( it != typeMetadataMap.end() ) 1020 | return it->second; 1021 | 1022 | return vector(); 1023 | } 1024 | 1025 | vector CScriptBuilder::GetMetadataForFunc(asIScriptFunction *func) 1026 | { 1027 | if( func ) 1028 | { 1029 | map >::iterator it = funcMetadataMap.find(func->GetId()); 1030 | if( it != funcMetadataMap.end() ) 1031 | return it->second; 1032 | } 1033 | 1034 | return vector(); 1035 | } 1036 | 1037 | vector CScriptBuilder::GetMetadataForVar(int varIdx) 1038 | { 1039 | map >::iterator it = varMetadataMap.find(varIdx); 1040 | if( it != varMetadataMap.end() ) 1041 | return it->second; 1042 | 1043 | return vector(); 1044 | } 1045 | 1046 | vector CScriptBuilder::GetMetadataForTypeProperty(int typeId, int varIdx) 1047 | { 1048 | map::iterator typeIt = classMetadataMap.find(typeId); 1049 | if(typeIt == classMetadataMap.end()) return vector(); 1050 | 1051 | map >::iterator propIt = typeIt->second.varMetadataMap.find(varIdx); 1052 | if(propIt == typeIt->second.varMetadataMap.end()) return vector(); 1053 | 1054 | return propIt->second; 1055 | } 1056 | 1057 | vector CScriptBuilder::GetMetadataForTypeMethod(int typeId, asIScriptFunction *method) 1058 | { 1059 | if( method ) 1060 | { 1061 | map::iterator typeIt = classMetadataMap.find(typeId); 1062 | if (typeIt == classMetadataMap.end()) return vector(); 1063 | 1064 | map >::iterator methodIt = typeIt->second.funcMetadataMap.find(method->GetId()); 1065 | if(methodIt == typeIt->second.funcMetadataMap.end()) return vector(); 1066 | 1067 | return methodIt->second; 1068 | } 1069 | 1070 | return vector(); 1071 | } 1072 | #endif 1073 | 1074 | string GetAbsolutePath(const string &file) 1075 | { 1076 | string str = file; 1077 | 1078 | // If this is a relative path, complement it with the current path 1079 | if( !((str.length() > 0 && (str[0] == '/' || str[0] == '\\')) || 1080 | str.find(":") != string::npos) ) 1081 | { 1082 | str = GetCurrentDir() + "/" + str; 1083 | } 1084 | 1085 | // Replace backslashes for forward slashes 1086 | size_t pos = 0; 1087 | while( (pos = str.find("\\", pos)) != string::npos ) 1088 | str[pos] = '/'; 1089 | 1090 | // Replace /./ with / 1091 | pos = 0; 1092 | while( (pos = str.find("/./", pos)) != string::npos ) 1093 | str.erase(pos+1, 2); 1094 | 1095 | // For each /../ remove the parent dir and the /../ 1096 | pos = 0; 1097 | while( (pos = str.find("/../")) != string::npos ) 1098 | { 1099 | size_t pos2 = str.rfind("/", pos-1); 1100 | if( pos2 != string::npos ) 1101 | str.erase(pos2, pos+3-pos2); 1102 | else 1103 | { 1104 | // The path is invalid 1105 | break; 1106 | } 1107 | } 1108 | 1109 | return str; 1110 | } 1111 | 1112 | string GetCurrentDir() 1113 | { 1114 | char buffer[1024]; 1115 | #if defined(_MSC_VER) || defined(_WIN32) 1116 | #ifdef _WIN32_WCE 1117 | static TCHAR apppath[MAX_PATH] = TEXT(""); 1118 | if (!apppath[0]) 1119 | { 1120 | GetModuleFileName(NULL, apppath, MAX_PATH); 1121 | 1122 | int appLen = _tcslen(apppath); 1123 | 1124 | // Look for the last backslash in the path, which would be the end 1125 | // of the path itself and the start of the filename. We only want 1126 | // the path part of the exe's full-path filename 1127 | // Safety is that we make sure not to walk off the front of the 1128 | // array (in case the path is nothing more than a filename) 1129 | while (appLen > 1) 1130 | { 1131 | if (apppath[appLen-1] == TEXT('\\')) 1132 | break; 1133 | appLen--; 1134 | } 1135 | 1136 | // Terminate the string after the trailing backslash 1137 | apppath[appLen] = TEXT('\0'); 1138 | } 1139 | #ifdef _UNICODE 1140 | wcstombs(buffer, apppath, min(1024, wcslen(apppath)*sizeof(wchar_t))); 1141 | #else 1142 | memcpy(buffer, apppath, min(1024, strlen(apppath))); 1143 | #endif 1144 | 1145 | return buffer; 1146 | #elif defined(__S3E__) 1147 | // Marmalade uses its own portable C library 1148 | return getcwd(buffer, (int)1024); 1149 | #elif _XBOX_VER >= 200 1150 | // XBox 360 doesn't support the getcwd function, just use the root folder 1151 | return "game:/"; 1152 | #elif defined(_M_ARM) 1153 | // TODO: How to determine current working dir on Windows Phone? 1154 | return ""; 1155 | #else 1156 | return _getcwd(buffer, (int)1024); 1157 | #endif // _MSC_VER 1158 | #elif defined(__APPLE__) || defined(__linux__) 1159 | return getcwd(buffer, 1024); 1160 | #else 1161 | return ""; 1162 | #endif 1163 | } 1164 | 1165 | END_AS_NAMESPACE 1166 | 1167 | 1168 | -------------------------------------------------------------------------------- /3rd/scriptbuilder/scriptbuilder.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRIPTBUILDER_H 2 | #define SCRIPTBUILDER_H 3 | 4 | //--------------------------- 5 | // Compilation settings 6 | // 7 | 8 | // Set this flag to turn on/off metadata processing 9 | // 0 = off 10 | // 1 = on 11 | #ifndef AS_PROCESS_METADATA 12 | #define AS_PROCESS_METADATA 1 13 | #endif 14 | 15 | // TODO: Implement flags for turning on/off include directives and conditional programming 16 | 17 | 18 | 19 | //--------------------------- 20 | // Declaration 21 | // 22 | 23 | #ifndef ANGELSCRIPT_H 24 | // Avoid having to inform include path if header is already include before 25 | #include 26 | #endif 27 | 28 | 29 | #if defined(_MSC_VER) && _MSC_VER <= 1200 30 | // disable the annoying warnings on MSVC 6 31 | #pragma warning (disable:4786) 32 | #endif 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include // _strcmpi 39 | 40 | BEGIN_AS_NAMESPACE 41 | 42 | class CScriptBuilder; 43 | 44 | // This callback will be called for each #include directive encountered by the 45 | // builder. The callback should call the AddSectionFromFile or AddSectionFromMemory 46 | // to add the included section to the script. If the include cannot be resolved 47 | // then the function should return a negative value to abort the compilation. 48 | typedef int (*INCLUDECALLBACK_t)(const char *include, const char *from, CScriptBuilder *builder, void *userParam); 49 | 50 | // This callback will be called for each #pragma directive encountered by the builder. 51 | // The application can interpret the pragmaText and decide what do to based on that. 52 | // If the callback returns a negative value the builder will report an error and abort the compilation. 53 | typedef int(*PRAGMACALLBACK_t)(const std::string &pragmaText, CScriptBuilder &builder, void *userParam); 54 | 55 | // Helper class for loading and pre-processing script files to 56 | // support include directives and metadata declarations 57 | class CScriptBuilder 58 | { 59 | public: 60 | CScriptBuilder(); 61 | 62 | // Start a new module 63 | int StartNewModule(asIScriptEngine *engine, const char *moduleName); 64 | 65 | // Load a script section from a file on disk 66 | // Returns 1 if the file was included 67 | // 0 if the file had already been included before 68 | // <0 on error 69 | int AddSectionFromFile(const char *filename); 70 | 71 | // Load a script section from memory 72 | // Returns 1 if the section was included 73 | // 0 if a section with the same name had already been included before 74 | // <0 on error 75 | int AddSectionFromMemory(const char *sectionName, 76 | const char *scriptCode, 77 | unsigned int scriptLength = 0, 78 | int lineOffset = 0); 79 | 80 | // Build the added script sections 81 | int BuildModule(); 82 | 83 | // Returns the engine 84 | asIScriptEngine *GetEngine(); 85 | 86 | // Returns the current module 87 | asIScriptModule *GetModule(); 88 | 89 | // Register the callback for resolving include directive 90 | void SetIncludeCallback(INCLUDECALLBACK_t callback, void *userParam); 91 | 92 | // Register the callback for resolving pragma directive 93 | void SetPragmaCallback(PRAGMACALLBACK_t callback, void *userParam); 94 | 95 | // Add a pre-processor define for conditional compilation 96 | void DefineWord(const char *word); 97 | 98 | // Enumerate included script sections 99 | unsigned int GetSectionCount() const; 100 | std::string GetSectionName(unsigned int idx) const; 101 | 102 | #if AS_PROCESS_METADATA == 1 103 | // Get metadata declared for classes, interfaces, and enums 104 | std::vector GetMetadataForType(int typeId); 105 | 106 | // Get metadata declared for functions 107 | std::vector GetMetadataForFunc(asIScriptFunction *func); 108 | 109 | // Get metadata declared for global variables 110 | std::vector GetMetadataForVar(int varIdx); 111 | 112 | // Get metadata declared for class variables 113 | std::vector GetMetadataForTypeProperty(int typeId, int varIdx); 114 | 115 | // Get metadata declared for class methods 116 | std::vector GetMetadataForTypeMethod(int typeId, asIScriptFunction *method); 117 | #endif 118 | 119 | protected: 120 | void ClearAll(); 121 | int Build(); 122 | int ProcessScriptSection(const char *script, unsigned int length, const char *sectionname, int lineOffset); 123 | int LoadScriptSection(const char *filename); 124 | bool IncludeIfNotAlreadyIncluded(const char *filename); 125 | 126 | int SkipStatement(int pos); 127 | 128 | int ExcludeCode(int start); 129 | void OverwriteCode(int start, int len); 130 | 131 | asIScriptEngine *engine; 132 | asIScriptModule *module; 133 | std::string modifiedScript; 134 | 135 | INCLUDECALLBACK_t includeCallback; 136 | void *includeParam; 137 | 138 | PRAGMACALLBACK_t pragmaCallback; 139 | void *pragmaParam; 140 | 141 | #if AS_PROCESS_METADATA == 1 142 | int ExtractMetadata(int pos, std::vector &outMetadata); 143 | int ExtractDeclaration(int pos, std::string &outName, std::string &outDeclaration, int &outType); 144 | 145 | enum METADATATYPE 146 | { 147 | MDT_TYPE = 1, 148 | MDT_FUNC = 2, 149 | MDT_VAR = 3, 150 | MDT_VIRTPROP = 4, 151 | MDT_FUNC_OR_VAR = 5 152 | }; 153 | 154 | // Temporary structure for storing metadata and declaration 155 | struct SMetadataDecl 156 | { 157 | SMetadataDecl(std::vector m, std::string n, std::string d, int t, std::string c, std::string ns) : metadata(m), name(n), declaration(d), type(t), parentClass(c), nameSpace(ns) {} 158 | std::vector metadata; 159 | std::string name; 160 | std::string declaration; 161 | int type; 162 | std::string parentClass; 163 | std::string nameSpace; 164 | }; 165 | std::vector foundDeclarations; 166 | std::string currentClass; 167 | std::string currentNamespace; 168 | 169 | // Storage of metadata for global declarations 170 | std::map > typeMetadataMap; 171 | std::map > funcMetadataMap; 172 | std::map > varMetadataMap; 173 | 174 | // Storage of metadata for class member declarations 175 | struct SClassMetadata 176 | { 177 | SClassMetadata(const std::string& aName) : className(aName) {} 178 | std::string className; 179 | std::map > funcMetadataMap; 180 | std::map > varMetadataMap; 181 | }; 182 | std::map classMetadataMap; 183 | 184 | #endif 185 | 186 | #ifdef _WIN32 187 | // On Windows the filenames are case insensitive so the comparisons to 188 | // avoid duplicate includes must also be case insensitive. True case insensitive 189 | // is not easy as it must be language aware, but a simple implementation such 190 | // as strcmpi should suffice in almost all cases. 191 | // 192 | // ref: http://www.gotw.ca/gotw/029.htm 193 | // ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd317761(v=vs.85).aspx 194 | // ref: http://site.icu-project.org/ 195 | 196 | // TODO: Strings by default are treated as UTF8 encoded. If the application choses to 197 | // use a different encoding, the comparison algorithm should be adjusted as well 198 | 199 | struct ci_less 200 | { 201 | bool operator()(const std::string &a, const std::string &b) const 202 | { 203 | return _stricmp(a.c_str(), b.c_str()) < 0; 204 | } 205 | }; 206 | std::set includedScripts; 207 | #else 208 | std::set includedScripts; 209 | #endif 210 | 211 | std::set definedWords; 212 | }; 213 | 214 | END_AS_NAMESPACE 215 | 216 | #endif 217 | -------------------------------------------------------------------------------- /3rd/scriptstdstring/scriptstdstring.h: -------------------------------------------------------------------------------- 1 | // 2 | // Script std::string 3 | // 4 | // This function registers the std::string type with AngelScript to be used as the default string type. 5 | // 6 | // The string type is registered as a value type, thus may have performance issues if a lot of 7 | // string operations are performed in the script. However, for relatively few operations, this should 8 | // not cause any problem for most applications. 9 | // 10 | 11 | #ifndef SCRIPTSTDSTRING_H 12 | #define SCRIPTSTDSTRING_H 13 | 14 | #ifndef ANGELSCRIPT_H 15 | // Avoid having to inform include path if header is already include before 16 | #include 17 | #endif 18 | 19 | #include 20 | 21 | //--------------------------- 22 | // Compilation settings 23 | // 24 | 25 | // Sometimes it may be desired to use the same method names as used by C++ STL. 26 | // This may for example reduce time when converting code from script to C++ or 27 | // back. 28 | // 29 | // 0 = off 30 | // 1 = on 31 | #ifndef AS_USE_STLNAMES 32 | #define AS_USE_STLNAMES 0 33 | #endif 34 | 35 | // Some prefer to use property accessors to get/set the length of the string 36 | // This option registers the accessors instead of the method length() 37 | #ifndef AS_USE_ACCESSORS 38 | #define AS_USE_ACCESSORS 0 39 | #endif 40 | 41 | BEGIN_AS_NAMESPACE 42 | 43 | void RegisterStdString(asIScriptEngine *engine); 44 | void RegisterStdStringUtils(asIScriptEngine *engine); 45 | 46 | END_AS_NAMESPACE 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /3rd/scriptstdstring/scriptstdstring_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "scriptstdstring.h" 3 | #include "../scriptarray/scriptarray.h" 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | BEGIN_AS_NAMESPACE 10 | 11 | // This function takes an input string and splits it into parts by looking 12 | // for a specified delimiter. Example: 13 | // 14 | // string str = "A|B||D"; 15 | // array@ array = str.split("|"); 16 | // 17 | // The resulting array has the following elements: 18 | // 19 | // {"A", "B", "", "D"} 20 | // 21 | // AngelScript signature: 22 | // array@ string::split(const string &in delim) const 23 | static CScriptArray *StringSplit(const string &delim, const string &str) 24 | { 25 | // Obtain a pointer to the engine 26 | asIScriptContext *ctx = asGetActiveContext(); 27 | asIScriptEngine *engine = ctx->GetEngine(); 28 | 29 | // TODO: This should only be done once 30 | // TODO: This assumes that CScriptArray was already registered 31 | asITypeInfo *arrayType = engine->GetTypeInfoByDecl("array"); 32 | 33 | // Create the array object 34 | CScriptArray *array = CScriptArray::Create(arrayType); 35 | 36 | // Find the existence of the delimiter in the input string 37 | int pos = 0, prev = 0, count = 0; 38 | while( (pos = (int)str.find(delim, prev)) != (int)string::npos ) 39 | { 40 | // Add the part to the array 41 | array->Resize(array->GetSize()+1); 42 | ((string*)array->At(count))->assign(&str[prev], pos-prev); 43 | 44 | // Find the next part 45 | count++; 46 | prev = pos + (int)delim.length(); 47 | } 48 | 49 | // Add the remaining part 50 | array->Resize(array->GetSize()+1); 51 | ((string*)array->At(count))->assign(&str[prev]); 52 | 53 | return array; 54 | } 55 | 56 | static void StringSplit_Generic(asIScriptGeneric *gen) 57 | { 58 | // Get the arguments 59 | string *str = (string*)gen->GetObject(); 60 | string *delim = *(string**)gen->GetAddressOfArg(0); 61 | 62 | // Return the array by handle 63 | *(CScriptArray**)gen->GetAddressOfReturnLocation() = StringSplit(*delim, *str); 64 | } 65 | 66 | 67 | 68 | // This function takes as input an array of string handles as well as a 69 | // delimiter and concatenates the array elements into one delimited string. 70 | // Example: 71 | // 72 | // array array = {"A", "B", "", "D"}; 73 | // string str = join(array, "|"); 74 | // 75 | // The resulting string is: 76 | // 77 | // "A|B||D" 78 | // 79 | // AngelScript signature: 80 | // string join(const array &in array, const string &in delim) 81 | static string StringJoin(const CScriptArray &array, const string &delim) 82 | { 83 | // Create the new string 84 | string str = ""; 85 | if( array.GetSize() ) 86 | { 87 | int n; 88 | for( n = 0; n < (int)array.GetSize() - 1; n++ ) 89 | { 90 | str += *(string*)array.At(n); 91 | str += delim; 92 | } 93 | 94 | // Add the last part 95 | str += *(string*)array.At(n); 96 | } 97 | 98 | return str; 99 | } 100 | 101 | static void StringJoin_Generic(asIScriptGeneric *gen) 102 | { 103 | // Get the arguments 104 | CScriptArray *array = *(CScriptArray**)gen->GetAddressOfArg(0); 105 | string *delim = *(string**)gen->GetAddressOfArg(1); 106 | 107 | // Return the string 108 | new(gen->GetAddressOfReturnLocation()) string(StringJoin(*array, *delim)); 109 | } 110 | 111 | // This is where the utility functions are registered. 112 | // The string type must have been registered first. 113 | void RegisterStdStringUtils(asIScriptEngine *engine) 114 | { 115 | int r; 116 | 117 | if( strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") ) 118 | { 119 | r = engine->RegisterObjectMethod("string", "array@ split(const string &in) const", asFUNCTION(StringSplit_Generic), asCALL_GENERIC); assert(r >= 0); 120 | r = engine->RegisterGlobalFunction("string join(const array &in, const string &in)", asFUNCTION(StringJoin_Generic), asCALL_GENERIC); assert(r >= 0); 121 | } 122 | else 123 | { 124 | r = engine->RegisterObjectMethod("string", "array@ split(const string &in) const", asFUNCTION(StringSplit), asCALL_CDECL_OBJLAST); assert(r >= 0); 125 | r = engine->RegisterGlobalFunction("string join(const array &in, const string &in)", asFUNCTION(StringJoin), asCALL_CDECL); assert(r >= 0); 126 | } 127 | } 128 | 129 | END_AS_NAMESPACE 130 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | include("3rd/HunterGate.cmake") 4 | HunterGate( 5 | URL "https://github.com/cpp-pm/hunter/archive/v0.23.251.tar.gz" 6 | SHA1 "5659b15dc0884d4b03dbd95710e6a1fa0fc3258d" 7 | ) 8 | 9 | project(angelscript-llvm LANGUAGES CXX) 10 | 11 | hunter_add_package(fmt) 12 | find_package(fmt CONFIG REQUIRED) 13 | 14 | if(DEFINED ENABLE_TESTING) 15 | hunter_add_package(Catch) 16 | find_package(Catch2 CONFIG REQUIRED) 17 | endif() 18 | 19 | add_library(angelscript-llvm 20 | src/asllvm/detail/ashelper.cpp 21 | src/asllvm/detail/assert.cpp 22 | src/asllvm/detail/builder.cpp 23 | src/asllvm/detail/debuginfo.cpp 24 | src/asllvm/detail/functionbuilder.cpp 25 | src/asllvm/detail/jitcompiler.cpp 26 | src/asllvm/detail/llvmglobals.cpp 27 | src/asllvm/detail/modulebuilder.cpp 28 | src/asllvm/detail/modulecommon.cpp 29 | src/asllvm/detail/modulemap.cpp 30 | src/asllvm/detail/runtime.cpp 31 | src/asllvm/detail/stackframe.cpp 32 | src/asllvm/jit.cpp 33 | ) 34 | 35 | if(NOT DEFINED ANGELSCRIPT_SDK_DIRECTORY) 36 | message( 37 | FATAL_ERROR 38 | "CMake variable ANGELSCRIPT_SDK_DIRECTORY must be specified to the sdk/ directory of your AS installation" 39 | ) 40 | endif() 41 | 42 | if(NOT IS_DIRECTORY "${ANGELSCRIPT_SDK_DIRECTORY}/angelscript/source") 43 | message( 44 | FATAL_ERROR 45 | "CMake variable ANGELSCRIPT_SDK_DIRECTORY does not appear to be valid: (path)/angelscript/source is not a directory" 46 | ) 47 | endif() 48 | 49 | target_compile_features(angelscript-llvm PUBLIC cxx_std_17) 50 | target_include_directories(angelscript-llvm PRIVATE include/ 51 | ${ANGELSCRIPT_SDK_DIRECTORY}/angelscript/source 52 | ${ANGELSCRIPT_SDK_DIRECTORY}/angelscript/include 53 | ) 54 | target_link_libraries(angelscript-llvm 55 | angelscript pthread 56 | fmt::fmt 57 | LLVM 58 | ) 59 | 60 | # TODO: enable only on gcc AND clang 61 | target_compile_options(angelscript-llvm PUBLIC 62 | "-fno-strict-aliasing" 63 | "-Wall" 64 | "-Wextra" 65 | ) 66 | 67 | add_subdirectory(3rd) 68 | 69 | if(DEFINED ENABLE_TESTING) 70 | add_subdirectory(tests/) 71 | endif() 72 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sylvain de Langen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asllvm 2 | 3 | A JIT compiler for AngelScript using LLVM and OrcV2. 4 | 5 | **asllvm is not production-ready and many features are not supported yet.** 6 | 7 | Note that it is not possible to partially JIT scripts so your application has to be fully supported to work with asllvm. 8 | 9 | ## Goals 10 | 11 | - Improving performance as much as possible. 12 | - Complete AngelScript support. 13 | - Symbol information for profiling tools and source-level debugging (implemented for `gdb` and `perf` already). 14 | 15 | ## Requirements 16 | 17 | You will need: 18 | - A C++17 compliant compiler. 19 | - A recent LLVM version (tested on LLVM9). 20 | 21 | Extra dependencies (`{fmt}` and `Catch2`) will be fetched automatically using `hunter`. 22 | 23 | ## Usage 24 | 25 | There is currently no simple usage example, but you can check [`tests/common.cpp`](tests/common.cpp) to get an idea. 26 | 27 | You have to register the JIT as usual. In its current state (this should be changed later on), you *need* to call 28 | `JitInterface::BuildModules` before any script call. 29 | 30 | ## Resources 31 | 32 | - **[Compatibility considerations (README!)](doc/compat.md)** 33 | - [Implementation status](doc/status.md) 34 | - [Performance recommendations when using asllvm](doc/performance.md) 35 | -------------------------------------------------------------------------------- /doc/compat.md: -------------------------------------------------------------------------------- 1 | # Compatibility-breaking behavior 2 | 3 | For your application to work properly, it must follow these requirements. 4 | 5 | ## All modules must be compiled by `asllvm` 6 | 7 | `asllvm` is not able to partially compile modules or compile modules optionally: 8 | - **All** functions within a module should be supported (see [status](status.md)). 9 | - **All** modules in your application should be compiled with JIT. 10 | 11 | ## Do not inspect the context state 12 | 13 | `asllvm` does not use the internal VM structures. Things like the callstack, context variables and parameters provided 14 | by AngelScript cannot be relied upon and will likely yield undefined behavior when used. 15 | 16 | There are however a few specific exceptions to this rule: 17 | - `asIScriptContext->m_callingSystemFunction` is populated before system calls. 18 | 19 | In general, this means that when the JIT is enabled, most things you would do with `asGetActiveContext()` cannot be 20 | used - e.g. the debugging add-on (note that `gdb` source-level debugging is an alternative for this usecase). 21 | -------------------------------------------------------------------------------- /doc/performance.md: -------------------------------------------------------------------------------- 1 | # Improving performance for generated code 2 | 3 | ## Use `final` whenever possible 4 | 5 | asllvm is able to statically dispatch virtual function calls in a specific scenario: Either the original method 6 | declaration either the class where the original method declaration resides must be `final`. 7 | 8 | This is an important optimization for code dealing with a lot of classes. A virtual function lookup is somewhat 9 | expensive and disallows some optimizations (such as inlining). 10 | 11 | For example, devirtualization will work in the following scenario: 12 | 13 | ```angelscript 14 | final class A 15 | { 16 | void foo() { print("hi"); } 17 | } 18 | 19 | void main() 20 | { 21 | A().foo(); 22 | } 23 | ``` 24 | 25 | but not in the following one: 26 | 27 | ```angelscript 28 | class A 29 | { 30 | void foo() { print("hi"); } 31 | } 32 | 33 | class B : A 34 | { 35 | final void foo() { print("hello"); } 36 | } 37 | 38 | void main() 39 | { 40 | B().foo(); 41 | } 42 | ``` 43 | 44 | (Note, devirtualization might be extended to support more cases in the future.) 45 | -------------------------------------------------------------------------------- /doc/status.md: -------------------------------------------------------------------------------- 1 | # Implementation status 2 | 3 | The following list is somewhat incomplete. 4 | 5 | ## General 6 | 7 | - [x] Generate, build, optimize and execute translated IR 8 | - [x] Source-level debugging\* 9 | - [ ] Complete bytecode support 10 | 11 | \*: Still fairly rough, but somewhat functional. 12 | 13 | ## Planned platform support 14 | 15 | Some specific features might be platform specific behavior and may need changes to port asllvm to other platforms, e.g.: 16 | - `CommonDefinitions::iptr` is always 64-bit even on 32-bit platforms. This should be detected as needed and used in 17 | more places than it currently is. 18 | - There may be unexpected C++ ABI differences between platforms, so generated external calls may be incorrect. 19 | 20 | - [x] x86-64 21 | - [x] Linux 22 | - [ ] Windows (MinGW) 23 | 24 | ## Bytecode and feature support 25 | 26 | This part is fairly incomplete, but provided to give a general idea: 27 | 28 | - [x] Integral arithmetic\* 29 | - [x] Floating-point arithmetic\* 30 | - [x] Variables 31 | - [x] Globals 32 | - [x] Branching (`if`, `for`, `while`, `switch` statements) 33 | - [x] Script function calls 34 | - [x] Regular functions 35 | - [ ] Imported functions 36 | - [ ] Function pointers 37 | - [x] Application interface 38 | - [x] Factories 39 | - [x] List constructors 40 | - [x] Object types 41 | - [x] Allocation 42 | - [x] Pass by value 43 | - [x] Pass by reference 44 | - [x] Return by value (from system function) 45 | - [x] Return by value (from script function) 46 | - [x] Virtual method calls 47 | - [ ] Composite calls 48 | - [x] Reference-counted types\*\*\*\* 49 | - [x] Function calls 50 | - [x] Script classes 51 | - [x] Constructing and destructing script classes 52 | - [x] Virtual script calls 53 | - [x] Devirtualization optimization\*\*\* 54 | - [x] Reference counted types\*\* 55 | - [ ] VM execution status support 56 | - [x] Exception on null pointer dereference 57 | - [ ] Exception on division by zero 58 | - [ ] Exception on overflow for some specific arithmetic ops 59 | - [ ] Support VM register introspection in system calls (for debugging, etc.) 60 | - [ ] VM suspend support 61 | - [ ] Handle application C++ exceptions 62 | - [ ] Script `try {} catch{}` blocks 63 | - [ ] Proper resource freeing on exceptions 64 | 65 | \*: `a ** b` (i.e. `pow`) is not implemented yet for any type. 66 | 67 | \*\*: Reference counting through handles is implemented as stubs and don't actually perform any freeing for now. 68 | 69 | \*\*\*: Implemented for trivial cases (method was originally declared as `final`). 70 | 71 | \*\*\*\*: Partial 72 | 73 | **Due to the design of asllvm, it is not possible to partially JIT modules, or to skip JITting for specific modules.** 74 | The JIT assumes that only supported features are used by your scripts and application interface, or it will yield an 75 | assertion failure or broken codegen. Any script call from JIT'd code *must* point to a JIT'd function. 76 | 77 | # Comparison, rationale and implementation status 78 | 79 | This project was partly created for learning purposes. 80 | 81 | An existing AngelScript JIT compiler from BlindMindStudios exists. I believe that asllvm may have several significant 82 | differences with BMS's JIT compiler. 83 | 84 | ## Source-level debugging 85 | 86 | asllvm currently has (limited) support for source-level debugging using gdb. This means that you can debug an 87 | AngelScript script in your IDE - inspect variables, step through, setup breakpoints, etc. with a consistent call stack. 88 | 89 | ## Cross-platform support 90 | 91 | BMS's JIT compiler was made with x86/x86-64 in mind, and cannot be ported to other architectures without a massive 92 | rewrite. 93 | 94 | asllvm is currently only implementing x86-64 Linux gcc support, but porting it to both x86 and x86-64 for Windows, Linux 95 | and macOS should not be complicated. 96 | 97 | x86-64 Linux and x86-64 Windows MinGW support is planned. 98 | 99 | Porting asllvm to other architectures is non-trivial, but feasible, but may require some trickery and debugging around 100 | the ABI support. 101 | 102 | ## Performance 103 | 104 | BMS's JIT compiler has good compile times, potentially better than asllvm, but the generated code is likely to be 105 | less efficient for several reasons: 106 | - BMS's JIT compiler does not really perform any optimization, whereas LLVM does (and does so quite well). 107 | - BMS's JIT compiler fallbacks to the VM in many occasions. asllvm aims to not ever require to use the AS VM as a 108 | fallback. 109 | - There are some optimizations potentially planned for asllvm that may simplify the generated logic. 110 | 111 | Note, however, that asllvm is a *much* larger dependency as it depends on LLVM. Think several tens of megabytes. 112 | -------------------------------------------------------------------------------- /include/asllvm/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace asllvm 4 | { 5 | struct JitConfig 6 | { 7 | //! \brief Enables LLVM optimizations. 8 | bool allow_llvm_optimizations : 1; 9 | 10 | //! \brief Allow aggressive floating-point arithmetic at the cost of precision. 11 | bool allow_fast_math : 1; 12 | 13 | //! \brief Allow optimization of script `final` functions and classes under certain circumstances. 14 | bool allow_devirtualization : 1; 15 | 16 | //! \brief 17 | //! Dangerous, proceed with caution. 18 | //! Allow assuming that `const` system methods do not modify memory visible from JIT'd code other than the 19 | //! parameters. 20 | //! \details 21 | //! This is a very aggressive optimization and may cause issues, but if your interface is designed so that this 22 | //! is a valid assumption to make, this can yield some benefits. 23 | //! 24 | //! In particular, this disallows const functions from: 25 | //! - Modifying global variables 26 | //! - Modifying any global state - e.g. i/o 27 | //! - Modifying `mutable` variables within the class. 28 | bool assume_const_is_pure : 1; 29 | 30 | //! \brief Whether to emit a lot of diagnostics for debugging. 31 | bool verbose : 1; 32 | 33 | // bool allow_late_jit_compiles : 1; 34 | 35 | JitConfig() : 36 | allow_llvm_optimizations{true}, 37 | allow_fast_math{true}, 38 | allow_devirtualization{true}, 39 | assume_const_is_pure{false}, 40 | verbose{false} /*, allow_late_jit_compiles{true}*/ 41 | {} 42 | }; 43 | } // namespace asllvm 44 | -------------------------------------------------------------------------------- /include/asllvm/detail/ashelper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace asllvm::detail 6 | { 7 | asCScriptFunction* get_nonvirtual_match(const asCScriptFunction& script_function); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /include/asllvm/detail/asinternalheaders.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma GCC diagnostic push 4 | #pragma GCC diagnostic ignored "-Wall" 5 | #pragma GCC diagnostic ignored "-Wextra" 6 | // Include order matters apparently (probably an AS bug: as_memory.h seems to be required by as_array.h) 7 | // clang-format off 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | // clang-format on 18 | #pragma GCC diagnostic pop 19 | -------------------------------------------------------------------------------- /include/asllvm/detail/assert.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef NDEBUG 4 | # define asllvm_assert(x) (void)0 5 | #else 6 | # define asllvm_assert(x) \ 7 | if (!(x)) \ 8 | { \ 9 | ::asllvm::detail::assert_failure_handler(#x, __FILE__, __LINE__); \ 10 | } \ 11 | (void)0 12 | #endif 13 | 14 | namespace asllvm::detail 15 | { 16 | [[noreturn]] void assert_failure_handler(const char* condition, const char* file, long line); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /include/asllvm/detail/builder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace asllvm::detail 14 | { 15 | struct StandardTypes 16 | { 17 | //! \brief asSVMRegisters type. 18 | llvm::Type* vm_registers; 19 | 20 | //! \brief void data type. 21 | llvm::Type* tvoid; 22 | 23 | llvm::IntegerType *i1, *i8, *i16, *i32, *i64, *iptr; 24 | 25 | llvm::Type* vm_state; 26 | 27 | llvm::Type *f32, *f64; 28 | 29 | // Shorthands for someCommonType->getPointerTo() 30 | llvm::Type *pvoid, *pi8, *pi16, *pi32, *pi64, *pf32, *pf64, *piptr; 31 | }; 32 | 33 | class Builder 34 | { 35 | public: 36 | Builder(JitCompiler& compiler); 37 | 38 | llvm::IRBuilder<>& ir() { return m_ir_builder; } 39 | StandardTypes& standard_types() { return m_types; } 40 | llvm::legacy::PassManager& optimizer() { return m_pass_manager; } 41 | llvm::orc::ThreadSafeContext& llvm_context() { return m_context; } 42 | 43 | llvm::Type* to_llvm_type(const asCDataType& type) const; 44 | 45 | private: 46 | StandardTypes setup_standard_types(); 47 | 48 | std::unique_ptr setup_context(); 49 | llvm::legacy::PassManager setup_pass_manager(); 50 | 51 | JitCompiler& m_compiler; 52 | 53 | llvm::orc::ThreadSafeContext m_context; 54 | llvm::legacy::PassManager m_pass_manager; 55 | llvm::IRBuilder<> m_ir_builder; 56 | 57 | StandardTypes m_types; 58 | 59 | mutable std::map m_object_types; 60 | }; 61 | 62 | } // namespace asllvm::detail 63 | -------------------------------------------------------------------------------- /include/asllvm/detail/bytecodeinstruction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace asllvm::detail 6 | { 7 | struct BytecodeInstruction 8 | { 9 | asDWORD* pointer; 10 | const asSBCInfo* info; 11 | std::size_t offset; 12 | 13 | asDWORD& arg_dword(std::size_t offset = 0) { return asBC_DWORDARG(pointer + offset); } 14 | int& arg_int(std::size_t offset = 0) { return asBC_INTARG(pointer + offset); } 15 | asQWORD& arg_qword(std::size_t offset = 0) { return asBC_QWORDARG(pointer + offset); } 16 | float& arg_float(std::size_t offset = 0) { return asBC_FLOATARG(pointer + offset); } 17 | asPWORD& arg_pword(std::size_t offset = 0) { return asBC_PTRARG(pointer + offset); } 18 | asWORD& arg_word0(std::size_t offset = 0) { return asBC_WORDARG0(pointer + offset); } 19 | asWORD& arg_word1(std::size_t offset = 0) { return asBC_WORDARG1(pointer + offset); } 20 | short& arg_sword0(std::size_t offset = 0) { return asBC_SWORDARG0(pointer + offset); } 21 | short& arg_sword1(std::size_t offset = 0) { return asBC_SWORDARG1(pointer + offset); } 22 | short& arg_sword2(std::size_t offset = 0) { return asBC_SWORDARG2(pointer + offset); } 23 | }; 24 | } // namespace asllvm::detail 25 | -------------------------------------------------------------------------------- /include/asllvm/detail/debuginfo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace asllvm::detail 7 | { 8 | struct SourceLocation 9 | { 10 | int line, column; 11 | }; 12 | 13 | SourceLocation get_source_location(FunctionContext context, std::size_t bytecode_offset = 0); 14 | llvm::DebugLoc get_debug_location(FunctionContext context, std::size_t bytecode_offset, llvm::DISubprogram* sp); 15 | } // namespace asllvm::detail 16 | -------------------------------------------------------------------------------- /include/asllvm/detail/functionbuilder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace asllvm::detail 17 | { 18 | class FunctionBuilder 19 | { 20 | struct PreprocessContext 21 | { 22 | long current_switch_offset; 23 | bool handling_jump_table = false; 24 | }; 25 | 26 | enum class GeneratedFunctionType 27 | { 28 | Implementation, 29 | VmEntryThunk 30 | }; 31 | 32 | struct VmEntryCallContext 33 | { 34 | llvm::Value *vm_frame_pointer = nullptr, *value_register = nullptr, *object_register = nullptr; 35 | }; 36 | 37 | public: 38 | //! \brief Constructor for FunctionBuilder, usually called by ModuleBuilder::create_function_builder(). 39 | FunctionBuilder(FunctionContext context); 40 | 41 | //! \brief Type used by AngelScript for local variable identifiers. 42 | //! \details 43 | //! - When <= 0, refers to a function parameter. The topmost element is the first passed parameter. 44 | //! - When >1, refers to the local function stack. 45 | //! \note 46 | //! Depending on the size of the first parameter, the offset of the first local value might be higher than 1. 47 | using StackVariableIdentifier = std::int16_t; 48 | 49 | // TODO: exceptions should make more sense than just a std::runtime_error 50 | //! \brief Generates the LLVM IR for _an entire function_ given its AngelScript bytecode. 51 | llvm::Function* translate_bytecode(asDWORD* bytecode, asUINT length); 52 | 53 | //! \brief Generates a function of asJITFunction signature. 54 | //! \details 55 | //! This interacts with the VM registers in order to dispatch the call to the function generated by 56 | //! read_bytecode() and return to the VM cleanly. 57 | llvm::Function* create_vm_entry_thunk(); 58 | 59 | private: 60 | //! \brief Handle a given bytecode instruction for preprocessing. 61 | //! \details 62 | //! The preprocessing stage is currently only used to populate certain internal structures as required by the 63 | //! further processing stage. 64 | //! In particular, this creates labels that are used for branching instructions. 65 | //! \see read_bytecode() 66 | void preprocess_instruction(BytecodeInstruction instruction, PreprocessContext& ctx); 67 | 68 | //! \brief Do the dirty work for the current bytecode instruction. 69 | //! \details 70 | //! This does most of the processing for bytecode instructions, namely, translating a bytecode instruction to 71 | //! IR. 72 | //! \warning 73 | //! This requires preprocess_instruction() to have been used beforehand. 74 | //! \see read_bytecode() 75 | void translate_instruction(BytecodeInstruction instruction); 76 | 77 | //! \brief Get a human-readable disassembly for a given bytecode instruction. 78 | std::string disassemble(BytecodeInstruction instruction); 79 | 80 | //! \brief Emit stack allocations for structures used locally within the current function. 81 | //! \see Populates m_locals and m_registers. 82 | void emit_allocate_local_structures(); 83 | 84 | //! \brief Implements a *signed* stack integer extend instruction from type \p source to type \p destination. 85 | void emit_cast( 86 | BytecodeInstruction instruction, 87 | llvm::Instruction::CastOps op, 88 | llvm::Type* source_type, 89 | llvm::Type* destination_type); 90 | 91 | //! \brief Implements an stack arithmetic instruction \p op with of type \p type. 92 | //! \details LHS and RHS will be determined from the stack. 93 | void emit_binop(BytecodeInstruction instruction, llvm::Instruction::BinaryOps op, llvm::Type* type); 94 | 95 | //! \brief Implements an stack arithmetic instruction \p op with of type \p type. 96 | //! \details LHS will be determined from the stack. 97 | void emit_binop(BytecodeInstruction instruction, llvm::Instruction::BinaryOps op, llvm::Value* rhs); 98 | 99 | //! \brief Implements an stack arithmetic instruction \p op with of type \p type. 100 | void 101 | emit_binop(BytecodeInstruction instruction, llvm::Instruction::BinaryOps op, llvm::Value* lhs, llvm::Value* rhs); 102 | 103 | void emit_neg(BytecodeInstruction instruction, llvm::Type* type); 104 | void emit_bit_not(BytecodeInstruction instruction, llvm::Type* type); 105 | void emit_condition(llvm::CmpInst::Predicate pred); 106 | 107 | void emit_increment(llvm::Type* value_type, long by); 108 | 109 | //! \brief Implements an integral comparison instruction. 110 | //! \details 111 | //! Stores compare(lhs, rhs) to the value register, with compare being: 112 | //! - `1` if `lhs > rhs` 113 | //! - `0` if `lhs == rhs` 114 | //! - `-1` if `lhs < rhs` 115 | void emit_compare(llvm::Value* lhs, llvm::Value* rhs, bool is_signed = true); 116 | 117 | //! \brief Performs the call to a non-script function \p function with parameters read from the stack. 118 | void emit_system_call(const asCScriptFunction& function); 119 | 120 | //! \brief Performs the call to the \p callee script function reading from the currently translated function. 121 | //! \returns The amount of DWORDs read. 122 | std::size_t emit_script_call(const asCScriptFunction& callee); 123 | 124 | // TODO: seems like this could be moved elsewhere? move vmentry codegen somewhere else? 125 | //! \brief Performs the call to the \p callee script function for a vm entry. 126 | //! \returns The amount of DWORDs read. 127 | std::size_t emit_script_call(const asCScriptFunction& callee, VmEntryCallContext ctx); 128 | 129 | //! \brief Performs the call to a script or system function \p function. 130 | void emit_call(const asCScriptFunction& function); 131 | 132 | //! \brief Match for asCScriptEngine::CallObjectMethod, for lack of a better name. 133 | void emit_object_method_call(const asCScriptFunction& function, llvm::Value* object); 134 | 135 | void emit_conditional_branch(BytecodeInstruction ins, llvm::CmpInst::Predicate predicate); 136 | 137 | llvm::Value* resolve_virtual_script_function(llvm::Value* script_object, const asCScriptFunction& callee); 138 | 139 | //! \brief Store \p value into the value register. 140 | //! \details 141 | //! Any data can be stored in the value register as long as it is less than 64-bit, otherwise, UB will occur. 142 | void store_value_register_value(llvm::Value* value); 143 | 144 | //! \brief Load a value of type \p type from the value register. 145 | //! \see store_value_register_value() 146 | llvm::Value* load_value_register_value(llvm::Type* type); 147 | 148 | //! \brief 149 | //! Get a pointer of type `type->getPointerTo()` to the value register, performing bit casting of the pointer if 150 | //! required. 151 | llvm::Value* get_value_register_pointer(llvm::Type* type); 152 | 153 | //! \brief Insert a basic block at bytecode offset \p offset. 154 | void insert_label(long offset); 155 | 156 | //! \brief Insert labels for the conditional jump bytecode instruction \p instruction. 157 | void preprocess_conditional_branch(BytecodeInstruction instruction); 158 | 159 | //! \brief Insert labels for the unconditional jump bytecode instruction \p instruction. 160 | void preprocess_unconditional_branch(BytecodeInstruction instruction); 161 | 162 | //! \brief 163 | //! Determine the llvm::BasicBlock that should be branched to for a successful conditional branch or for an 164 | //! unconditional branch of the jump instruction \p instruction. 165 | llvm::BasicBlock* get_branch_target(BytecodeInstruction instruction); 166 | 167 | //! \brief 168 | //! Determine the llvm::BasicBlock that should be branched to for a false conditional branch for the conditional 169 | //! jump instruction \p instruction. 170 | llvm::BasicBlock* get_conditional_fail_branch_target(BytecodeInstruction instruction); 171 | 172 | //! \brief 173 | //! Changes the insert point of the IR generator to the new \p block and emit an unconditional branch to 174 | //! \p block from the old one if necessary. 175 | void switch_to_block(llvm::BasicBlock* block); 176 | 177 | void create_function_debug_info(llvm::Function* function, GeneratedFunctionType type); 178 | 179 | llvm::Value* load_global(asPWORD address, llvm::Type* type); 180 | 181 | //! \brief 182 | //! If i1 value is true, then the state_if_true vm state will be set. 183 | void emit_check_boolean(llvm::Value* value, llvm::Value* state_if_true); 184 | 185 | void emit_check_null_pointer(llvm::Value* pointer); 186 | void emit_check_vm_state(llvm::Value* state); // TODO: better naming for this one <- 187 | void emit_check_context_state(); 188 | void emit_vm_exception_return(llvm::Value* state); 189 | 190 | FunctionContext m_context; 191 | 192 | //! \brief Kind of the function being generated right now. 193 | GeneratedFunctionType m_generated_type; 194 | 195 | //! \brief 196 | //! Value register, used to temporarily store small (<= 64-bit) values (and sometimes for returning data from 197 | //! functions). 198 | llvm::AllocaInst* m_value_register; 199 | 200 | //! \brief 201 | //! Object register, a temporary register to hold objects. 202 | llvm::AllocaInst* m_object_register; 203 | 204 | StackFrame m_stack; 205 | 206 | //! \brief Map from a bytecode offset to a BasicBlock. 207 | //! \see InstructionContext::offset 208 | std::map m_jump_map; 209 | 210 | //! \brief Map from the bytecode offset of a asBC_JMPP opcode (used for jump tables) to the offsets of the targets. 211 | //! \see InstructionContext::offset 212 | std::map> m_switch_map; 213 | 214 | //! \brief Pointer to the RET instruction. 215 | //! \details AngelScript bytecode functions only use RET once, we can thus assume to have only one exit point. 216 | asDWORD* m_ret_pointer = nullptr; 217 | }; 218 | 219 | } // namespace asllvm::detail 220 | -------------------------------------------------------------------------------- /include/asllvm/detail/functioncontext.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace asllvm::detail 8 | { 9 | struct FunctionContext 10 | { 11 | JitCompiler* compiler; 12 | ModuleBuilder* module_builder; 13 | llvm::Function* llvm_function; 14 | const asCScriptFunction* script_function; 15 | }; 16 | } // namespace asllvm::detail 17 | -------------------------------------------------------------------------------- /include/asllvm/detail/fwd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace asllvm 4 | { 5 | struct JitConfig; 6 | class JitInterface; 7 | 8 | namespace detail 9 | { 10 | struct BytecodeInstruction; 11 | struct LibraryInitializer; 12 | struct StandardTypes; 13 | class JitCompiler; 14 | class Builder; 15 | class FunctionBuilder; 16 | class ModuleBuilder; 17 | class ModuleMap; 18 | } // namespace detail 19 | } // namespace asllvm 20 | -------------------------------------------------------------------------------- /include/asllvm/detail/jitcompiler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace asllvm::detail 12 | { 13 | struct LibraryInitializer 14 | { 15 | LibraryInitializer(); 16 | }; 17 | 18 | class JitCompiler 19 | { 20 | public: 21 | JitCompiler(JitConfig config = {}); 22 | 23 | int jit_compile(asIScriptFunction* function, asJITFunction* output); 24 | void jit_free(asJITFunction function); 25 | 26 | asCScriptEngine& engine() { return *m_engine; } 27 | llvm::orc::LLJIT& jit() { return *m_jit; } 28 | const JitConfig& config() const { return m_config; } 29 | Builder& builder() { return m_builder; } 30 | 31 | void diagnostic(const std::string& text, asEMsgType message_type = asMSGTYPE_INFORMATION) const; 32 | 33 | void build_modules(); 34 | 35 | private: 36 | std::unique_ptr setup_jit(); 37 | 38 | void dump_state() const; 39 | 40 | [[no_unique_address]] LibraryInitializer m_llvm_initializer; 41 | 42 | llvm::JITEventListener* m_gdb_listener; 43 | #if LLVM_USE_PERF 44 | llvm::JITEventListener* m_perf_listener; 45 | #endif 46 | std::unique_ptr m_jit; 47 | 48 | asCScriptEngine* m_engine = nullptr; 49 | JitConfig m_config; 50 | Builder m_builder; 51 | ModuleMap m_module_map; 52 | }; 53 | 54 | } // namespace asllvm::detail 55 | -------------------------------------------------------------------------------- /include/asllvm/detail/llvmglobals.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace asllvm::detail 7 | { 8 | extern llvm::ExitOnError ExitOnError; 9 | } // namespace asllvm::detail 10 | -------------------------------------------------------------------------------- /include/asllvm/detail/modulebuilder.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO: we can get rid of this dependency, we just need some trickery due to std::unique_ptr requiring the definition 4 | // of llvm::Module to be present because of the deleter. this can be worked around. 5 | // this is included in jit.hpp, we do not want LLVM stuff included there. 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace asllvm::detail 20 | { 21 | struct StandardFunctions 22 | { 23 | llvm::FunctionCallee alloc, free, new_script_object, script_vtable_lookup, system_vtable_lookup, call_object_method, 24 | panic, set_internal_exception, prepare_system_call, check_execution_status; 25 | }; 26 | 27 | struct GlobalVariables 28 | {}; 29 | 30 | struct PendingFunction 31 | { 32 | asCScriptFunction* function; 33 | asJITFunction* jit_function; 34 | }; 35 | 36 | struct JitSymbol 37 | { 38 | asCScriptFunction* script_function; 39 | std::string name, entry_name; 40 | asJITFunction* jit_function; 41 | }; 42 | 43 | struct ModuleDebugInfo 44 | { 45 | llvm::DICompileUnit* compile_unit; 46 | llvm::DIFile* file; 47 | 48 | using AsTypeIdentifier = int; 49 | std::unordered_map type_cache; 50 | }; 51 | 52 | class ModuleBuilder 53 | { 54 | public: 55 | ModuleBuilder(JitCompiler& compiler, asIScriptModule* module = nullptr); 56 | 57 | void append(PendingFunction function); 58 | 59 | llvm::Function* get_script_function(const asCScriptFunction& function); 60 | llvm::FunctionType* get_script_function_type(const asCScriptFunction& script_function); 61 | 62 | llvm::Function* get_system_function(const asCScriptFunction& system_function); 63 | llvm::FunctionType* get_system_function_type(const asCScriptFunction& system_function); 64 | 65 | llvm::DIType* get_debug_type(ModuleDebugInfo::AsTypeIdentifier type); 66 | 67 | void build(); 68 | void link(); 69 | 70 | llvm::Module& module() { return *m_llvm_module; } 71 | StandardFunctions& standard_functions() { return m_internal_functions; } 72 | GlobalVariables& global_variables() { return m_global_variables; } 73 | 74 | llvm::DIBuilder& di_builder() { return *m_di_builder; } 75 | ModuleDebugInfo& debug_info() { return m_debug_info; } 76 | 77 | void dump_state() const; 78 | 79 | private: 80 | bool is_exposed_directly(asIScriptFunction& function) const; 81 | 82 | ModuleDebugInfo setup_debug_info(); 83 | StandardFunctions setup_runtime(); 84 | GlobalVariables setup_global_variables(); 85 | 86 | void build_functions(); 87 | void link_symbols(); 88 | 89 | JitCompiler& m_compiler; 90 | asIScriptModule* m_script_module; 91 | std::unique_ptr m_llvm_module; 92 | std::unique_ptr m_di_builder; 93 | ModuleDebugInfo m_debug_info; 94 | std::vector m_pending_functions; 95 | std::vector m_jit_functions; 96 | std::map m_script_functions; 97 | std::map m_system_functions; 98 | StandardFunctions m_internal_functions; 99 | GlobalVariables m_global_variables; 100 | }; 101 | 102 | } // namespace asllvm::detail 103 | -------------------------------------------------------------------------------- /include/asllvm/detail/modulecommon.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace asllvm::detail 8 | { 9 | std::string make_module_name(const asIScriptModule* module); 10 | std::string make_function_name(const asIScriptFunction& function); 11 | std::string make_vm_entry_thunk_name(const asIScriptFunction& function); 12 | std::string make_system_function_name(const asIScriptFunction& function); 13 | std::string make_debug_name(const asIScriptFunction& function); 14 | 15 | constexpr asPWORD vtable_userdata_identifier = 0xCAFECAFECAFECAFE; 16 | } // namespace asllvm::detail 17 | -------------------------------------------------------------------------------- /include/asllvm/detail/modulemap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace asllvm::detail 12 | { 13 | class ModuleMap 14 | { 15 | public: 16 | ModuleMap(JitCompiler& compiler); 17 | 18 | ModuleBuilder& operator[](asIScriptModule* module); 19 | 20 | void build_modules(); 21 | 22 | void dump_state() const; 23 | 24 | private: 25 | JitCompiler& m_compiler; 26 | 27 | ModuleBuilder m_shared_module_builder; 28 | std::unordered_map m_map; 29 | }; 30 | 31 | } // namespace asllvm::detail 32 | -------------------------------------------------------------------------------- /include/asllvm/detail/runtime.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace asllvm::detail::runtime 7 | { 8 | void* script_vtable_lookup(asCScriptObject* object, asCScriptFunction* function); 9 | void* system_vtable_lookup(void* object, asPWORD func); 10 | void call_object_method(void* object, asCScriptFunction* function); 11 | void* new_script_object(asCObjectType* object_type); 12 | [[noreturn]] void panic(); 13 | void set_internal_exception(VmState state); 14 | void prepare_system_call(asCScriptFunction* callee); 15 | VmState check_execution_status(); 16 | } // namespace asllvm::detail::runtime 17 | -------------------------------------------------------------------------------- /include/asllvm/detail/stackframe.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace asllvm::detail 9 | { 10 | struct Parameter 11 | { 12 | //! \brief Index of the argument in the argument list of the translated function. 13 | std::size_t argument_index = 0; 14 | 15 | //! \brief 16 | //! Local alloca variable where the LLVM argument is stored, which is useful as we often store a pointer to 17 | //! a parameter or modify it. 18 | llvm::AllocaInst* local_alloca = nullptr; 19 | 20 | //! \brief AngelScript type id for this parameter. 21 | int type_id = 0; 22 | 23 | //! \brief Name that shows up in DWARF debug info. 24 | const char* debug_name = ""; 25 | }; 26 | 27 | class StackFrame 28 | { 29 | public: 30 | //! \brief A stack offset as defined within the AngelScript bytecode. 31 | //! \details 32 | //! A stack offset can refer to different areas: 33 | //! - `offset <= 0`: Parameters. See m_parameters. 34 | //! - `0 < offset <= variable_space()`: Local variables. See m_storage. 35 | //! - `variable_space() < offset <= total_space()`: Temporary stack storage. 36 | //! Can be `== variable_space` when full. 37 | using AsStackOffset = long; 38 | 39 | StackFrame(FunctionContext context); 40 | 41 | void setup(); 42 | void finalize(); 43 | 44 | long variable_space() const; 45 | long stack_space() const; 46 | long total_space() const; 47 | 48 | AsStackOffset current_stack_pointer() const; 49 | void ugly_hack_stack_pointer_within_bounds(); 50 | bool empty_stack() const; 51 | 52 | void check_stack_pointer_bounds(); 53 | 54 | void push(llvm::Value* value, std::size_t dwords); 55 | void pop(std::size_t dwords); 56 | llvm::Value* pop(std::size_t dwords, llvm::Type* type); 57 | llvm::Value* top(llvm::Type* type); 58 | 59 | llvm::Value* load(AsStackOffset offset, llvm::Type* type); 60 | void store(AsStackOffset offset, llvm::Value* value); 61 | 62 | llvm::Value* pointer_to(AsStackOffset offset, llvm::Type* pointee_type); 63 | llvm::Value* pointer_to(AsStackOffset offset); 64 | 65 | llvm::AllocaInst* storage_alloca(); 66 | 67 | private: 68 | void allocate_parameter_storage(); 69 | void emit_debug_info(); 70 | 71 | FunctionContext m_context; 72 | 73 | //! \brief Array of DWORDs used as local storage for bytecode operations. 74 | //! \details 75 | //! This array can really be thought to be split in two: 76 | //! 1. Locals, which are addressed relative to the frame pointer (loaded at a fixed index) 77 | //! 2. The temporary stack, which is used, among other things, to push parameters to pass to functions. 78 | //! \see AsStackOffset 79 | llvm::AllocaInst* m_storage; 80 | 81 | //! \brief Mapping from offsets within this stack frame to parameters. 82 | //! \see AsStackOffset 83 | std::map m_parameters; 84 | 85 | //! \brief Current stack pointer, relevant during IR generation. 86 | //! \see asSVMRegisters::stackPointer 87 | AsStackOffset m_stack_pointer; 88 | }; 89 | } // namespace asllvm::detail 90 | -------------------------------------------------------------------------------- /include/asllvm/detail/vmstate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace asllvm::detail 6 | { 7 | enum class VmState : std::uint8_t 8 | { 9 | Ok = 0, 10 | ExceptionExternal, 11 | ExceptionNullPointer 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /include/asllvm/jit.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace asllvm 9 | { 10 | class JitInterface final : public asIJITCompiler 11 | { 12 | public: 13 | JitInterface(JitConfig flags = {}); 14 | 15 | virtual int CompileFunction(asIScriptFunction* function, asJITFunction* output) override; 16 | virtual void ReleaseJITFunction(asJITFunction func) override; 17 | 18 | void BuildModules(); 19 | 20 | private: 21 | std::unique_ptr m_compiler; 22 | }; 23 | } // namespace asllvm 24 | -------------------------------------------------------------------------------- /src/asllvm/detail/allocationmanager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/asllvm/detail/ashelper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | asCScriptFunction* asllvm::detail::get_nonvirtual_match(const asCScriptFunction& script_function) 6 | { 7 | // TODO: find a way to make it less garbage 8 | const auto method_count = script_function.objectType->GetMethodCount(); 9 | 10 | const std::string declaration = script_function.GetDeclaration(true, true, false); 11 | 12 | for (std::size_t i = 0; i < method_count; ++i) 13 | { 14 | auto& potential_match 15 | = *static_cast(script_function.objectType->GetMethodByIndex(i, false)); 16 | 17 | if (declaration == potential_match.GetDeclaration(true, true, false)) 18 | { 19 | return &potential_match; 20 | } 21 | } 22 | 23 | return nullptr; 24 | } 25 | -------------------------------------------------------------------------------- /src/asllvm/detail/assert.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace asllvm::detail 7 | { 8 | void assert_failure_handler(const char* condition, const char* file, long line) 9 | { 10 | fmt::print(stderr, "{}:{}: asllvm assertion ({}) failed\n", file, line, condition); 11 | std::abort(); 12 | } 13 | } // namespace asllvm::detail 14 | -------------------------------------------------------------------------------- /src/asllvm/detail/builder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace asllvm::detail 16 | { 17 | Builder::Builder(JitCompiler& compiler) : 18 | m_compiler{compiler}, 19 | m_context{setup_context()}, 20 | m_pass_manager{setup_pass_manager()}, 21 | m_ir_builder{*m_context.getContext()}, 22 | m_types{setup_standard_types()} 23 | { 24 | llvm::FastMathFlags fast_fp; 25 | 26 | if (m_compiler.config().allow_fast_math) 27 | { 28 | fast_fp.set(); 29 | } 30 | 31 | m_ir_builder.setFastMathFlags(fast_fp); 32 | } 33 | 34 | llvm::Type* Builder::to_llvm_type(const asCDataType& type) const 35 | { 36 | if (type.IsPrimitive()) 37 | { 38 | llvm::Type* base_type = nullptr; 39 | 40 | switch (type.GetTokenType()) 41 | { 42 | case ttVoid: base_type = m_types.tvoid; break; 43 | case ttBool: base_type = m_types.i1; break; 44 | case ttInt8: 45 | case ttUInt8: base_type = m_types.i8; break; 46 | case ttInt16: 47 | case ttUInt16: base_type = m_types.i16; break; 48 | case ttInt: 49 | case ttUInt: base_type = m_types.i32; break; 50 | case ttInt64: 51 | case ttUInt64: base_type = m_types.i64; break; 52 | case ttFloat: base_type = m_types.f32; break; 53 | case ttDouble: base_type = m_types.f64; break; 54 | default: asllvm_assert(false && "provided primitive type not supported"); 55 | } 56 | 57 | if (type.IsReference()) 58 | { 59 | return base_type->getPointerTo(); 60 | } 61 | 62 | return base_type; 63 | } 64 | 65 | if (type.IsReference() || type.IsObjectHandle() || type.IsObject()) 66 | { 67 | asllvm_assert(type.GetTypeInfo() != nullptr); 68 | auto& object_type = static_cast(*type.GetTypeInfo()); 69 | const int type_id = object_type.GetTypeId(); 70 | 71 | if (const auto it = m_object_types.find(type_id); it != m_object_types.end()) 72 | { 73 | return it->second->getPointerTo(); 74 | } 75 | 76 | llvm::StructType* struct_type = llvm::StructType::create( 77 | {llvm::ArrayType::get(m_types.i8, type.GetSizeInMemoryBytes())}, object_type.GetName()); 78 | 79 | m_object_types.emplace(type_id, struct_type); 80 | 81 | // TODO: what makes most sense between declaring this as non-const and having a non-mutable m_object_types 82 | // versus this as a const and m_object_types as mutable? 83 | return struct_type->getPointerTo(); 84 | } 85 | 86 | asllvm_assert(false && "type not supported"); 87 | } 88 | 89 | StandardTypes Builder::setup_standard_types() 90 | { 91 | StandardTypes types{}; 92 | 93 | auto context_lock = m_context.getLock(); 94 | auto& context = *m_context.getContext(); 95 | 96 | types.tvoid = llvm::Type::getVoidTy(context); 97 | types.i1 = llvm::Type::getInt1Ty(context); 98 | types.i8 = llvm::Type::getInt8Ty(context); 99 | types.i16 = llvm::Type::getInt16Ty(context); 100 | types.i32 = llvm::Type::getInt32Ty(context); 101 | types.i64 = llvm::Type::getInt64Ty(context); 102 | types.iptr = llvm::Type::getInt64Ty(context); // TODO: determine pointer type from target machine 103 | types.vm_state = types.i8; 104 | types.f32 = llvm::Type::getFloatTy(context); 105 | types.f64 = llvm::Type::getDoubleTy(context); 106 | 107 | types.pvoid = types.i8->getPointerTo(); 108 | types.pi8 = types.i8->getPointerTo(); 109 | types.pi16 = types.i16->getPointerTo(); 110 | types.pi32 = types.i32->getPointerTo(); 111 | types.pi64 = types.i64->getPointerTo(); 112 | types.piptr = types.iptr->getPointerTo(); 113 | types.pf32 = types.f32->getPointerTo(); 114 | types.pf64 = types.f64->getPointerTo(); 115 | 116 | { 117 | types.vm_registers = llvm::StructType::create( 118 | { 119 | types.pi32, // programPointer 120 | types.pi32, // stackFramePointer 121 | types.pi32, // stackPointer 122 | types.iptr, // valueRegister 123 | types.pvoid, // objectRegister 124 | types.pvoid, // objectType - todo asITypeInfo 125 | types.i1, // doProcessSuspend 126 | types.pvoid, // ctx - todo asIScriptContext 127 | }, 128 | "asSVMRegisters"); 129 | } 130 | 131 | return types; 132 | } 133 | 134 | std::unique_ptr Builder::setup_context() { return std::make_unique(); } 135 | 136 | llvm::legacy::PassManager Builder::setup_pass_manager() 137 | { 138 | constexpr int inlining_threshold = 275; 139 | 140 | llvm::legacy::PassManager pm; 141 | pm.add(llvm::createVerifierPass()); 142 | 143 | if (m_compiler.config().allow_llvm_optimizations) 144 | { 145 | llvm::PassManagerBuilder pmb; 146 | pmb.OptLevel = 3; 147 | pmb.Inliner = llvm::createFunctionInliningPass(inlining_threshold); 148 | pmb.DisableUnrollLoops = false; 149 | pmb.LoopVectorize = true; 150 | pmb.SLPVectorize = true; 151 | pmb.populateModulePassManager(pm); 152 | pm.add(llvm::createVerifierPass()); // Verify the optimized IR as well 153 | } 154 | 155 | return pm; 156 | } 157 | } // namespace asllvm::detail 158 | -------------------------------------------------------------------------------- /src/asllvm/detail/debuginfo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace asllvm::detail 5 | { 6 | SourceLocation get_source_location(FunctionContext context, std::size_t bytecode_offset) 7 | { 8 | int section; 9 | const int encoded_line 10 | = const_cast(context.script_function)->GetLineNumber(bytecode_offset, §ion); 11 | 12 | const int line = encoded_line & 0xFFFFF, column = encoded_line >> 20; 13 | return {line, column}; 14 | } 15 | 16 | llvm::DebugLoc get_debug_location(FunctionContext context, std::size_t bytecode_offset, llvm::DISubprogram* sp) 17 | { 18 | const SourceLocation loc = get_source_location(context, bytecode_offset); 19 | const auto di_loc = llvm::DILocation::get(context.llvm_function->getContext(), loc.line, loc.column, sp); 20 | return llvm::DebugLoc(di_loc); 21 | } 22 | } // namespace asllvm::detail 23 | -------------------------------------------------------------------------------- /src/asllvm/detail/jitcompiler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #if !LLVM_USE_PERF 15 | # pragma message("warning: LLVM was not build with perf support. Disabling perf listener support") 16 | #endif 17 | 18 | namespace asllvm::detail 19 | { 20 | LibraryInitializer::LibraryInitializer() 21 | { 22 | llvm::InitializeNativeTarget(); 23 | llvm::InitializeNativeTargetAsmPrinter(); 24 | } 25 | 26 | JitCompiler::JitCompiler(JitConfig config) : 27 | m_llvm_initializer{}, 28 | m_gdb_listener{llvm::JITEventListener::createGDBRegistrationListener()}, 29 | #if LLVM_USE_PERF 30 | m_perf_listener{llvm::JITEventListener::createPerfJITEventListener()}, 31 | #endif 32 | m_jit{setup_jit()}, 33 | m_config{config}, 34 | m_builder{*this}, 35 | m_module_map{*this} 36 | {} 37 | 38 | int JitCompiler::jit_compile(asIScriptFunction* function, asJITFunction* output) 39 | { 40 | asllvm_assert( 41 | !(m_engine != nullptr && function->GetEngine() != m_engine) 42 | && "JIT compiler expects to be used against the same asIScriptEngine during its lifetime"); 43 | 44 | m_engine = static_cast(function->GetEngine()); 45 | 46 | m_module_map[function->GetModule()].append({static_cast(function), output}); 47 | return 0; 48 | } 49 | 50 | void JitCompiler::jit_free(asJITFunction func) 51 | { 52 | // TODO 53 | } 54 | 55 | void JitCompiler::diagnostic(const std::string& text, asEMsgType message_type) const 56 | { 57 | asllvm_assert(m_engine != nullptr); 58 | 59 | std::string edited_text = "asllvm: "; 60 | edited_text += text; 61 | 62 | m_engine->WriteMessage("", 0, 0, message_type, edited_text.c_str()); 63 | } 64 | 65 | void JitCompiler::build_modules() { m_module_map.build_modules(); } 66 | 67 | std::unique_ptr JitCompiler::setup_jit() 68 | { 69 | auto jit = ExitOnError(llvm::orc::LLJITBuilder().create()); 70 | 71 | auto& object_linking_layer = static_cast(jit->getObjLinkingLayer()); 72 | object_linking_layer.setNotifyLoaded([this]( 73 | [[maybe_unused]] llvm::orc::MaterializationResponsibility& a, 74 | const llvm::object::ObjectFile& b, 75 | const llvm::RuntimeDyld::LoadedObjectInfo& c) { 76 | m_gdb_listener->notifyObjectLoaded( 77 | reinterpret_cast(&b), 78 | b, 79 | c 80 | ); 81 | 82 | #if LLVM_USE_PERF 83 | m_perf_listener->notifyObjectLoaded(a, b, c); 84 | #endif 85 | }); 86 | 87 | object_linking_layer.setProcessAllSections(true); 88 | 89 | return jit; 90 | } 91 | 92 | void JitCompiler::dump_state() const { m_module_map.dump_state(); } 93 | 94 | } // namespace asllvm::detail 95 | -------------------------------------------------------------------------------- /src/asllvm/detail/llvmglobals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace asllvm::detail 4 | { 5 | llvm::ExitOnError ExitOnError; 6 | } // namespace asllvm::detail 7 | -------------------------------------------------------------------------------- /src/asllvm/detail/modulebuilder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace asllvm::detail 20 | { 21 | ModuleBuilder::ModuleBuilder(JitCompiler& compiler, asIScriptModule* module) : 22 | m_compiler{compiler}, 23 | m_script_module{module}, 24 | m_llvm_module{ 25 | std::make_unique(make_module_name(module), *compiler.builder().llvm_context().getContext())}, 26 | m_di_builder{std::make_unique(*m_llvm_module)}, 27 | m_debug_info{setup_debug_info()}, 28 | m_internal_functions{setup_runtime()}, 29 | m_global_variables{setup_global_variables()} 30 | {} 31 | 32 | void ModuleBuilder::append(PendingFunction function) { m_pending_functions.push_back(function); } 33 | 34 | llvm::Function* ModuleBuilder::get_script_function(const asCScriptFunction& function) 35 | { 36 | asllvm_assert( 37 | function.vfTableIdx < 0 38 | && "Virtual functions should not be handled at this level. Resolve the virtual function first."); 39 | 40 | if (auto it = m_script_functions.find(function.GetId()); it != m_script_functions.end()) 41 | { 42 | return it->second; 43 | } 44 | 45 | if (m_compiler.config().verbose) 46 | { 47 | m_compiler.diagnostic(fmt::format( 48 | "creating function {}: signature {}, object type {}", 49 | function.GetId(), 50 | function.GetDeclaration(true, true, true), 51 | function.objectType != nullptr ? function.objectType->GetName() : "(null)")); 52 | } 53 | 54 | llvm::Function* internal_function = llvm::Function::Create( 55 | get_script_function_type(function), 56 | llvm::Function::ExternalLinkage, 57 | make_function_name(function), 58 | *m_llvm_module); 59 | 60 | m_script_functions.emplace(function.GetId(), internal_function); 61 | return internal_function; 62 | } 63 | 64 | llvm::FunctionType* ModuleBuilder::get_script_function_type(const asCScriptFunction& script_function) 65 | { 66 | asCScriptEngine& engine = m_compiler.engine(); 67 | Builder& builder = m_compiler.builder(); 68 | StandardTypes& types = builder.standard_types(); 69 | 70 | const auto parameter_count = script_function.parameterTypes.GetLength(); 71 | 72 | std::vector parameter_types; 73 | 74 | // TODO: make sret 75 | if (script_function.returnType.GetTokenType() != ttVoid) 76 | { 77 | if (script_function.DoesReturnOnStack()) 78 | { 79 | parameter_types.push_back(builder.to_llvm_type(script_function.returnType)); 80 | } 81 | else 82 | { 83 | parameter_types.push_back(builder.to_llvm_type(script_function.returnType)->getPointerTo()); 84 | } 85 | } 86 | 87 | if (asCObjectType* object_type = script_function.objectType; object_type != nullptr) 88 | { 89 | parameter_types.push_back( 90 | builder.to_llvm_type(engine.GetDataTypeFromTypeId(script_function.objectType->GetTypeId()))); 91 | } 92 | 93 | for (std::size_t i = 0; i < parameter_count; ++i) 94 | { 95 | parameter_types.push_back(builder.to_llvm_type(script_function.parameterTypes[i])); 96 | } 97 | 98 | return llvm::FunctionType::get(types.vm_state, parameter_types, false); 99 | } 100 | 101 | llvm::Function* ModuleBuilder::get_system_function(const asCScriptFunction& system_function) 102 | { 103 | asSSystemFunctionInterface& intf = *system_function.sysFuncIntf; 104 | 105 | const int id = system_function.GetId(); 106 | if (auto it = m_system_functions.find(id); it != m_system_functions.end()) 107 | { 108 | return it->second; 109 | } 110 | 111 | llvm::Function* function = llvm::Function::Create( 112 | get_system_function_type(system_function), 113 | llvm::Function::ExternalLinkage, 114 | 0, 115 | make_system_function_name(system_function), 116 | m_llvm_module.get()); 117 | 118 | if (intf.hostReturnInMemory) 119 | { 120 | function->addParamAttr(0, llvm::Attribute::StructRet); 121 | } 122 | 123 | if (m_compiler.config().assume_const_is_pure && system_function.IsReadOnly()) 124 | { 125 | function->addFnAttr(llvm::Attribute::InaccessibleMemOrArgMemOnly); 126 | } 127 | 128 | m_system_functions.emplace(id, function); 129 | 130 | return function; 131 | } 132 | 133 | llvm::FunctionType* ModuleBuilder::get_system_function_type(const asCScriptFunction& system_function) 134 | { 135 | StandardTypes& types = m_compiler.builder().standard_types(); 136 | asSSystemFunctionInterface& intf = *system_function.sysFuncIntf; 137 | 138 | llvm::Type* return_type = types.tvoid; 139 | 140 | const std::size_t param_count = system_function.GetParamCount(); 141 | std::vector parameter_types; 142 | 143 | if (intf.hostReturnInMemory) 144 | { 145 | // types[0] 146 | parameter_types.push_back(m_compiler.builder().to_llvm_type(system_function.returnType)->getPointerTo()); 147 | } 148 | else 149 | { 150 | return_type = m_compiler.builder().to_llvm_type(system_function.returnType); 151 | } 152 | 153 | for (std::size_t i = 0; i < param_count; ++i) 154 | { 155 | parameter_types.push_back(m_compiler.builder().to_llvm_type(system_function.parameterTypes[i])); 156 | } 157 | 158 | switch (intf.callConv) 159 | { 160 | // thiscall: add this as first parameter 161 | // FIXME: this is ABI-specific - make it easy to handle different ABIs 162 | case ICC_VIRTUAL_THISCALL: 163 | case ICC_THISCALL: 164 | case ICC_CDECL_OBJFIRST: 165 | { 166 | parameter_types.insert(parameter_types.begin(), types.pvoid); 167 | break; 168 | } 169 | 170 | case ICC_CDECL_OBJLAST: 171 | { 172 | parameter_types.push_back(types.pvoid); 173 | break; 174 | } 175 | 176 | // C calling convention: nothing special to do 177 | case ICC_CDECL: break; 178 | 179 | default: asllvm_assert(false && "unsupported calling convention"); 180 | } 181 | 182 | return llvm::FunctionType::get(return_type, parameter_types, false); 183 | } 184 | 185 | llvm::DIType* ModuleBuilder::get_debug_type(ModuleDebugInfo::AsTypeIdentifier script_type_id) 186 | { 187 | asCScriptEngine& engine = m_compiler.engine(); 188 | 189 | if (const auto it = m_debug_info.type_cache.find(script_type_id); it != m_debug_info.type_cache.end()) 190 | { 191 | return it->second; 192 | } 193 | 194 | const auto add = [&](llvm::DIType* debug_type) { 195 | m_debug_info.type_cache.emplace(script_type_id, debug_type); 196 | return debug_type; 197 | }; 198 | 199 | switch (script_type_id) 200 | { 201 | case asTYPEID_VOID: return add(m_di_builder->createUnspecifiedType("void")); 202 | 203 | case asTYPEID_BOOL: return add(m_di_builder->createBasicType("bool", 1, llvm::dwarf::DW_ATE_boolean)); 204 | 205 | case asTYPEID_INT8: return add(m_di_builder->createBasicType("int8", 8, llvm::dwarf::DW_ATE_signed)); 206 | case asTYPEID_INT16: return add(m_di_builder->createBasicType("int16", 16, llvm::dwarf::DW_ATE_signed)); 207 | case asTYPEID_INT32: return add(m_di_builder->createBasicType("int", 32, llvm::dwarf::DW_ATE_signed)); 208 | case asTYPEID_INT64: return add(m_di_builder->createBasicType("int64", 64, llvm::dwarf::DW_ATE_signed)); 209 | 210 | case asTYPEID_UINT8: return add(m_di_builder->createBasicType("uint8", 8, llvm::dwarf::DW_ATE_unsigned)); 211 | case asTYPEID_UINT16: return add(m_di_builder->createBasicType("uint16", 16, llvm::dwarf::DW_ATE_unsigned)); 212 | case asTYPEID_UINT32: return add(m_di_builder->createBasicType("uint", 32, llvm::dwarf::DW_ATE_unsigned)); 213 | case asTYPEID_UINT64: return add(m_di_builder->createBasicType("uint64", 64, llvm::dwarf::DW_ATE_unsigned)); 214 | 215 | case asTYPEID_FLOAT: return add(m_di_builder->createBasicType("float", 32, llvm::dwarf::DW_ATE_float)); 216 | case asTYPEID_DOUBLE: return add(m_di_builder->createBasicType("double", 64, llvm::dwarf::DW_ATE_float)); 217 | } 218 | 219 | asITypeInfo* type_info = engine.GetTypeInfoById(script_type_id); 220 | asllvm_assert(type_info != nullptr); 221 | 222 | if (const auto flags = type_info->GetFlags(); (flags & asOBJ_SCRIPT_OBJECT) != 0) 223 | { 224 | const auto& object_type = *static_cast(type_info); 225 | 226 | std::vector llvm_properties; 227 | 228 | const auto& properties = object_type.properties; 229 | for (std::size_t i = 0; i < properties.GetLength(); ++i) 230 | { 231 | const auto& property = properties[i]; 232 | 233 | llvm_properties.push_back(m_di_builder->createMemberType( 234 | nullptr, 235 | &property->name[0], 236 | nullptr, 237 | 0, 238 | property->type.GetSizeInMemoryBytes() * 8, 239 | 0, 240 | property->byteOffset * 8, 241 | llvm::DINode::FlagPublic, 242 | get_debug_type(engine.GetTypeIdFromDataType(property->type)))); 243 | } 244 | 245 | llvm::DIType* class_type = m_di_builder->createClassType( 246 | nullptr, 247 | object_type.GetName(), 248 | nullptr, 249 | 0, 250 | object_type.GetSize(), 251 | 0, 252 | 0, 253 | llvm::DINode::FlagTypePassByReference, 254 | nullptr, 255 | m_di_builder->getOrCreateArray(llvm_properties)); 256 | 257 | return m_di_builder->createPointerType(class_type, AS_PTR_SIZE * 4 * 8); 258 | } 259 | else if ((flags & (asOBJ_REF | asOBJ_VALUE)) != 0) 260 | { 261 | return add( 262 | m_di_builder->createBasicType(type_info->GetName(), AS_PTR_SIZE * 4 * 8, llvm::dwarf::DW_ATE_address)); 263 | } 264 | 265 | return add(m_di_builder->createUnspecifiedType("")); 266 | } 267 | 268 | void ModuleBuilder::build() 269 | { 270 | build_functions(); 271 | 272 | if (m_compiler.config().verbose) 273 | { 274 | dump_state(); 275 | } 276 | 277 | link_symbols(); 278 | 279 | m_compiler.builder().optimizer().run(*m_llvm_module); 280 | 281 | ExitOnError(m_compiler.jit().addIRModule( 282 | llvm::orc::ThreadSafeModule(std::move(m_llvm_module), m_compiler.builder().llvm_context()))); 283 | } 284 | 285 | void ModuleBuilder::link() 286 | { 287 | for (JitSymbol& symbol : m_jit_functions) 288 | { 289 | auto function = ExitOnError(m_compiler.jit().lookup(symbol.name)); 290 | symbol.script_function->SetUserData(reinterpret_cast(function.getAddress()), vtable_userdata_identifier); 291 | 292 | auto entry = ExitOnError(m_compiler.jit().lookup(symbol.entry_name)); 293 | *symbol.jit_function = reinterpret_cast(entry.getAddress()); 294 | } 295 | } 296 | 297 | void ModuleBuilder::dump_state() const 298 | { 299 | for (const auto& function : m_llvm_module->functions()) 300 | { 301 | fmt::print(stderr, "Function '{}'\n", function.getName().str()); 302 | } 303 | 304 | m_llvm_module->print(llvm::errs(), nullptr); 305 | } 306 | 307 | ModuleDebugInfo ModuleBuilder::setup_debug_info() 308 | { 309 | ModuleDebugInfo debug_info; 310 | 311 | debug_info.file 312 | = m_di_builder->createFile(m_script_module != nullptr ? m_script_module->GetName() : "", ""); 313 | 314 | debug_info.compile_unit = m_di_builder->createCompileUnit( 315 | llvm::dwarf::DW_LANG_C_plus_plus, 316 | debug_info.file, 317 | "asllvm", 318 | m_compiler.config().allow_llvm_optimizations, 319 | "", 320 | 0); 321 | 322 | return debug_info; 323 | } 324 | 325 | StandardFunctions ModuleBuilder::setup_runtime() 326 | { 327 | StandardTypes& types = m_compiler.builder().standard_types(); 328 | 329 | StandardFunctions funcs{}; 330 | 331 | const auto linkage = llvm::Function::ExternalLinkage; 332 | 333 | { 334 | llvm::Function* function = llvm::Function::Create( 335 | llvm::FunctionType::get(types.pvoid, {types.iptr}, false), 336 | linkage, 337 | "asllvm.private.alloc", 338 | m_llvm_module.get()); 339 | 340 | // The object we created is unique as it was dynamically allocated 341 | function->addAttribute(0, llvm::Attribute::NoAlias); 342 | 343 | function->setOnlyAccessesInaccessibleMemory(); 344 | 345 | funcs.alloc = function; 346 | } 347 | 348 | { 349 | llvm::Function* function = llvm::Function::Create( 350 | llvm::FunctionType::get(types.tvoid, {types.pvoid}, false), 351 | linkage, 352 | "asllvm.private.free", 353 | m_llvm_module.get()); 354 | 355 | funcs.free = function; 356 | } 357 | 358 | { 359 | llvm::Function* function = llvm::Function::Create( 360 | llvm::FunctionType::get(types.pvoid, {types.pvoid}, false), 361 | linkage, 362 | "asllvm.private.new_script_object", 363 | m_llvm_module.get()); 364 | 365 | function->setOnlyAccessesInaccessibleMemOrArgMem(); 366 | 367 | // The object we created is unique as it was dynamically allocated 368 | function->addAttribute(0, llvm::Attribute::NoAlias); 369 | 370 | funcs.new_script_object = function; 371 | } 372 | 373 | { 374 | llvm::Function* function = llvm::Function::Create( 375 | llvm::FunctionType::get(types.pvoid, {types.pvoid, types.pvoid}, false), 376 | linkage, 377 | "asllvm.private.script_vtable_lookup", 378 | m_llvm_module.get()); 379 | 380 | funcs.script_vtable_lookup = function; 381 | } 382 | 383 | { 384 | llvm::Function* function = llvm::Function::Create( 385 | llvm::FunctionType::get(types.pvoid, {types.pvoid, types.pvoid}, false), 386 | linkage, 387 | 0, 388 | "asllvm.private.system_vtable_lookup", 389 | m_llvm_module.get()); 390 | 391 | funcs.system_vtable_lookup = function; 392 | } 393 | 394 | { 395 | llvm::Function* function = llvm::Function::Create( 396 | llvm::FunctionType::get(types.tvoid, {types.pvoid, types.pvoid}, false), 397 | linkage, 398 | "asllvm.private.call_object_method", 399 | m_llvm_module.get()); 400 | 401 | funcs.call_object_method = function; 402 | } 403 | 404 | { 405 | llvm::Function* function = llvm::Function::Create( 406 | llvm::FunctionType::get(types.tvoid, {}, false), linkage, "asllvm.private.panic", m_llvm_module.get()); 407 | 408 | function->setDoesNotReturn(); 409 | 410 | funcs.panic = function; 411 | } 412 | 413 | { 414 | llvm::Function* function = llvm::Function::Create( 415 | llvm::FunctionType::get(types.tvoid, {types.vm_state}, false), 416 | linkage, 417 | "asllvm.private.set_internal_exception", 418 | m_llvm_module.get()); 419 | 420 | funcs.set_internal_exception = function; 421 | } 422 | 423 | { 424 | llvm::Function* function = llvm::Function::Create( 425 | llvm::FunctionType::get(types.tvoid, {types.pvoid}, false), 426 | linkage, 427 | "asllvm.private.prepare_system_call", 428 | m_llvm_module.get()); 429 | 430 | funcs.prepare_system_call = function; 431 | } 432 | 433 | { 434 | llvm::Function* function = llvm::Function::Create( 435 | llvm::FunctionType::get(types.vm_state, {}, false), 436 | linkage, 437 | "asllvm.private.check_execution_status", 438 | m_llvm_module.get()); 439 | 440 | funcs.check_execution_status = function; 441 | } 442 | 443 | return funcs; 444 | } 445 | 446 | GlobalVariables ModuleBuilder::setup_global_variables() 447 | { 448 | GlobalVariables globals; 449 | 450 | return globals; 451 | } 452 | 453 | void ModuleBuilder::build_functions() 454 | { 455 | for (const auto& pending : m_pending_functions) 456 | { 457 | if (std::find_if( 458 | m_jit_functions.begin(), 459 | m_jit_functions.end(), 460 | [&](JitSymbol symbol) { return symbol.script_function == pending.function; }) 461 | != m_jit_functions.end()) 462 | { 463 | if (m_compiler.config().verbose) 464 | { 465 | m_compiler.diagnostic("ignoring function that was compiled in module already"); 466 | } 467 | 468 | continue; 469 | } 470 | 471 | FunctionContext context; 472 | context.compiler = &m_compiler; 473 | context.module_builder = this; 474 | context.script_function = static_cast(pending.function); 475 | context.llvm_function = get_script_function(*static_cast(pending.function)); 476 | 477 | FunctionBuilder builder{context}; 478 | 479 | asUINT length; 480 | asDWORD* bytecode = pending.function->GetByteCode(&length); 481 | 482 | builder.translate_bytecode(bytecode, length); 483 | 484 | llvm::Function* entry = builder.create_vm_entry_thunk(); 485 | 486 | JitSymbol symbol; 487 | symbol.script_function = pending.function; 488 | symbol.name = make_function_name(*pending.function); // TODO: get it from somewhere 489 | symbol.entry_name = entry->getName(); 490 | symbol.jit_function = pending.jit_function; 491 | m_jit_functions.push_back(symbol); 492 | 493 | m_di_builder->finalize(); 494 | } 495 | 496 | m_pending_functions.clear(); 497 | } 498 | 499 | void ModuleBuilder::link_symbols() 500 | { 501 | llvm::orc::MangleAndInterner mangle( 502 | m_compiler.jit().getExecutionSession(), 503 | m_compiler.jit().getDataLayout() 504 | ); 505 | 506 | llvm::orc::SymbolMap symbols; 507 | 508 | auto& dylib = m_compiler.jit().getMainJITDylib(); 509 | 510 | const auto define_function = [&](auto& function, llvm::StringRef name) { 511 | if (m_compiler.jit().lookup(name)) 512 | { 513 | return; 514 | } 515 | 516 | llvm::JITEvaluatedSymbol symbol( 517 | llvm::pointerToJITTargetAddress(function), 518 | llvm::JITSymbolFlags::Callable | 519 | llvm::JITSymbolFlags::Absolute 520 | ); 521 | 522 | symbols.insert({mangle(name), symbol}); 523 | }; 524 | 525 | for (const auto& it : m_system_functions) 526 | { 527 | auto& script_func = static_cast(*m_compiler.engine().GetFunctionById(it.first)); 528 | define_function(script_func.sysFuncIntf->func, it.second->getName()); 529 | } 530 | 531 | // TODO: figure out why func->getName() returns an empty string 532 | define_function(*userAlloc, "asllvm.private.alloc"); 533 | define_function(*userFree, "asllvm.private.free"); 534 | define_function(runtime::new_script_object, "asllvm.private.new_script_object"); 535 | define_function(runtime::script_vtable_lookup, "asllvm.private.script_vtable_lookup"); 536 | define_function(runtime::system_vtable_lookup, "asllvm.private.system_vtable_lookup"); 537 | define_function(runtime::call_object_method, "asllvm.private.call_object_method"); 538 | define_function(runtime::panic, "asllvm.private.panic"); 539 | define_function(runtime::set_internal_exception, "asllvm.private.set_internal_exception"); 540 | define_function(runtime::prepare_system_call, "asllvm.private.prepare_system_call"); 541 | define_function(runtime::check_execution_status, "asllvm.private.check_execution_status"); 542 | 543 | define_function(fmodf, "fmodf"); 544 | define_function(fmod, "fmod"); 545 | 546 | ExitOnError(dylib.define(llvm::orc::absoluteSymbols(symbols))); 547 | } 548 | } // namespace asllvm::detail 549 | -------------------------------------------------------------------------------- /src/asllvm/detail/modulecommon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace asllvm::detail 7 | { 8 | std::string make_module_name(const asIScriptModule* module) 9 | { 10 | if (module == nullptr) 11 | { 12 | return "asllvm.shared"; 13 | } 14 | 15 | return fmt::format("asllvm.module.{}", module->GetName()); 16 | } 17 | 18 | std::string make_function_name(const asIScriptFunction& function) 19 | { 20 | return fmt::format("{}.{}", make_module_name(function.GetModule()), function.GetDeclaration(true, true, false)); 21 | } 22 | 23 | std::string make_vm_entry_thunk_name(const asIScriptFunction& function) 24 | { 25 | return make_function_name(function) + ".vmthunk"; 26 | } 27 | 28 | std::string make_system_function_name(const asIScriptFunction& function) 29 | { 30 | return fmt::format("asllvm.external.{}", function.GetDeclaration(true, true, false)); 31 | } 32 | 33 | std::string make_debug_name(const asIScriptFunction& function) 34 | { 35 | std::string name; 36 | 37 | asllvm_assert(function.GetNamespace() != nullptr); 38 | 39 | // We do this regardless of whether the namespace is "" 40 | name += function.GetNamespace(); 41 | name += "::"; 42 | 43 | if (const asITypeInfo* info = function.GetObjectType(); info != nullptr) 44 | { 45 | name += info->GetName(); 46 | name += "::"; 47 | } 48 | 49 | name += function.GetName(); 50 | 51 | return name; 52 | } 53 | } // namespace asllvm::detail 54 | -------------------------------------------------------------------------------- /src/asllvm/detail/modulemap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace asllvm::detail 9 | { 10 | ModuleMap::ModuleMap(JitCompiler& compiler) : m_compiler{compiler}, m_shared_module_builder(m_compiler) {} 11 | 12 | ModuleBuilder& ModuleMap::operator[](asIScriptModule* module) 13 | { 14 | if (module == nullptr) 15 | { 16 | return m_shared_module_builder; 17 | } 18 | 19 | const char* name = module->GetName(); 20 | 21 | if (auto it = m_map.find(name); it != m_map.end()) 22 | { 23 | return it->second; 24 | } 25 | 26 | const auto [it, success] = m_map.emplace(std::string(name), ModuleBuilder{m_compiler, module}); 27 | 28 | return it->second; 29 | } 30 | 31 | void ModuleMap::build_modules() 32 | { 33 | if (m_compiler.config().verbose) 34 | { 35 | m_compiler.diagnostic("building modules"); 36 | } 37 | 38 | m_shared_module_builder.build(); 39 | for (auto& it : m_map) 40 | { 41 | it.second.build(); 42 | } 43 | 44 | if (m_compiler.config().verbose) 45 | { 46 | m_compiler.diagnostic("linking modules"); 47 | } 48 | 49 | m_shared_module_builder.link(); 50 | for (auto& it : m_map) 51 | { 52 | it.second.link(); 53 | } 54 | 55 | m_map.clear(); 56 | } 57 | 58 | void ModuleMap::dump_state() const 59 | { 60 | for (const auto& [name, module_builder] : m_map) 61 | { 62 | fmt::print(stderr, "\nModule '{}':\n", name); 63 | module_builder.dump_state(); 64 | } 65 | } 66 | 67 | } // namespace asllvm::detail 68 | -------------------------------------------------------------------------------- /src/asllvm/detail/runtime.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace asllvm::detail::runtime 7 | { 8 | void* script_vtable_lookup(asCScriptObject* object, asCScriptFunction* function) 9 | { 10 | auto& object_type = *static_cast(object->GetObjectType()); 11 | return reinterpret_cast( 12 | object_type.virtualFunctionTable[function->vfTableIdx]->GetUserData(vtable_userdata_identifier)); 13 | } 14 | 15 | void* system_vtable_lookup(void* object, asPWORD func) 16 | { 17 | // TODO: this likely does not have to be a function 18 | #if defined(__linux__) && defined(__x86_64__) 19 | using FunctionPtr = asQWORD (*)(); 20 | auto vftable = *(reinterpret_cast(object)); 21 | return reinterpret_cast(vftable[func >> 3]); 22 | #else 23 | # error("virtual function lookups unsupported for this target") 24 | #endif 25 | } 26 | 27 | void call_object_method(void* object, asCScriptFunction* function) 28 | { 29 | // TODO: this is not very efficient: this performs an extra call into AS that is more generic than we require: we 30 | // know a lot of stuff about the function at JIT time. 31 | auto& engine = *static_cast(function->GetEngine()); 32 | engine.CallObjectMethod(object, function->GetId()); 33 | } 34 | 35 | void* new_script_object(asCObjectType* object_type) 36 | { 37 | auto* object = static_cast(userAlloc(object_type->size)); 38 | ScriptObject_Construct(object_type, object); 39 | return object; 40 | } 41 | 42 | void panic() { std::abort(); } 43 | 44 | void set_internal_exception(VmState state) 45 | { 46 | asCContext* context = static_cast(asGetActiveContext()); 47 | 48 | switch (state) 49 | { 50 | case VmState::ExceptionExternal: break; 51 | case VmState::ExceptionNullPointer: context->SetInternalException(TXT_NULL_POINTER_ACCESS); break; 52 | default: asllvm_assert(false && "unexpected"); 53 | } 54 | } 55 | 56 | void prepare_system_call(asCScriptFunction* callee) 57 | { 58 | asCContext* context = static_cast(asGetActiveContext()); 59 | 60 | context->m_callingSystemFunction = callee; 61 | } 62 | 63 | VmState check_execution_status() 64 | { 65 | switch (asGetActiveContext()->GetState()) 66 | { 67 | case asEXECUTION_EXCEPTION: 68 | case asEXECUTION_ERROR: return VmState::ExceptionExternal; 69 | default: break; 70 | } 71 | 72 | return VmState::Ok; 73 | } 74 | } // namespace asllvm::detail::runtime 75 | -------------------------------------------------------------------------------- /src/asllvm/detail/stackframe.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace asllvm::detail 12 | { 13 | StackFrame::StackFrame(FunctionContext context) : m_context{context} {} 14 | 15 | void StackFrame::setup() 16 | { 17 | Builder& builder = m_context.compiler->builder(); 18 | llvm::IRBuilder<>& ir = builder.ir(); 19 | StandardTypes& types = builder.standard_types(); 20 | 21 | m_storage = ir.CreateAlloca(llvm::ArrayType::get(types.i32, total_space()), nullptr, "storage"); 22 | allocate_parameter_storage(); 23 | 24 | m_stack_pointer = variable_space(); 25 | 26 | emit_debug_info(); 27 | } 28 | 29 | void StackFrame::finalize() { asllvm_assert(empty_stack()); } 30 | 31 | long StackFrame::variable_space() const { return m_context.script_function->scriptData->variableSpace; } 32 | 33 | long StackFrame::stack_space() const 34 | { 35 | // 2 pointers reserved for exception handling, 1 for asBC_ALLOC. See RESERVE_STACK in as_context.cpp. 36 | constexpr long reserved_space = 2 * AS_PTR_SIZE; 37 | 38 | return m_context.script_function->scriptData->stackNeeded - variable_space() + reserved_space; 39 | } 40 | 41 | long StackFrame::total_space() const { return variable_space() + stack_space(); } 42 | 43 | StackFrame::AsStackOffset StackFrame::current_stack_pointer() const { return m_stack_pointer; } 44 | 45 | void StackFrame::ugly_hack_stack_pointer_within_bounds() 46 | { 47 | m_stack_pointer = std::max(m_stack_pointer, variable_space()); 48 | } 49 | 50 | bool StackFrame::empty_stack() const { return m_stack_pointer == variable_space(); } 51 | 52 | void StackFrame::check_stack_pointer_bounds() 53 | { 54 | asllvm_assert(m_stack_pointer >= variable_space()); 55 | asllvm_assert(m_stack_pointer <= total_space()); 56 | } 57 | 58 | void StackFrame::push(llvm::Value* value, std::size_t dwords) 59 | { 60 | m_stack_pointer += dwords; 61 | store(m_stack_pointer, value); 62 | } 63 | 64 | void StackFrame::pop(std::size_t dwords) { m_stack_pointer -= dwords; } 65 | 66 | llvm::Value* StackFrame::pop(std::size_t dwords, llvm::Type* type) 67 | { 68 | llvm::Value* value = load(m_stack_pointer, type); 69 | pop(dwords); 70 | 71 | return value; 72 | } 73 | 74 | llvm::Value* StackFrame::top(llvm::Type* type) { return load(m_stack_pointer, type); } 75 | 76 | llvm::Value* StackFrame::load(StackFrame::AsStackOffset offset, llvm::Type* type) 77 | { 78 | Builder& builder = m_context.compiler->builder(); 79 | llvm::IRBuilder<>& ir = builder.ir(); 80 | 81 | return ir.CreateLoad(type, pointer_to(offset, type), fmt::format("local@{}.value", offset)); 82 | } 83 | 84 | void StackFrame::store(StackFrame::AsStackOffset offset, llvm::Value* value) 85 | { 86 | Builder& builder = m_context.compiler->builder(); 87 | llvm::IRBuilder<>& ir = builder.ir(); 88 | 89 | ir.CreateStore(value, pointer_to(offset, value->getType())); 90 | } 91 | 92 | llvm::Value* StackFrame::pointer_to(StackFrame::AsStackOffset offset, llvm::Type* pointee_type) 93 | { 94 | Builder& builder = m_context.compiler->builder(); 95 | llvm::IRBuilder<>& ir = builder.ir(); 96 | 97 | llvm::Value* pointer = pointer_to(offset); 98 | return ir.CreatePointerCast(pointer, pointee_type->getPointerTo(), fmt::format("local@{}.castedptr", offset)); 99 | } 100 | 101 | llvm::Value* StackFrame::pointer_to(StackFrame::AsStackOffset offset) 102 | { 103 | Builder& builder = m_context.compiler->builder(); 104 | llvm::IRBuilder<>& ir = builder.ir(); 105 | StandardTypes& types = builder.standard_types(); 106 | 107 | // Value at stack offset is a parameter 108 | if (offset <= 0) 109 | { 110 | return m_parameters.at(offset).local_alloca; 111 | } 112 | 113 | // Value at stack offset is within the stack 114 | const long real_offset = total_space() - offset; 115 | 116 | // Ensure that offset is within alloca boundaries 117 | // TODO: this should check for the pointee type as well 118 | asllvm_assert(real_offset >= 0); 119 | asllvm_assert(real_offset <= total_space()); 120 | 121 | return ir.CreateInBoundsGEP( 122 | m_storage, 123 | {llvm::ConstantInt::get(types.iptr, 0), llvm::ConstantInt::get(types.iptr, real_offset)}, 124 | fmt::format("local@{}.ptr", offset)); 125 | } 126 | 127 | llvm::AllocaInst* StackFrame::storage_alloca() { return m_storage; } 128 | 129 | void StackFrame::allocate_parameter_storage() 130 | { 131 | Builder& builder = m_context.compiler->builder(); 132 | llvm::IRBuilder<>& ir = builder.ir(); 133 | asCScriptEngine& engine = m_context.compiler->engine(); 134 | 135 | AsStackOffset stack_offset = 0; 136 | auto argument_it = m_context.llvm_function->arg_begin(); 137 | 138 | const auto allocate_parameter = [&](const asCDataType& data_type, const char* name) -> void { 139 | Parameter parameter; 140 | parameter.argument_index = std::distance(m_context.llvm_function->arg_begin(), argument_it); 141 | parameter.local_alloca = ir.CreateAlloca(builder.to_llvm_type(data_type), nullptr, name); 142 | parameter.type_id = engine.GetTypeIdFromDataType(data_type); 143 | parameter.debug_name = name; 144 | 145 | const auto [it, success] = m_parameters.emplace(stack_offset, parameter); 146 | asllvm_assert(success); 147 | 148 | ir.CreateStore(argument_it, parameter.local_alloca); 149 | 150 | stack_offset -= data_type.GetSizeOnStackDWords(); 151 | ++argument_it; 152 | }; 153 | 154 | if (m_context.script_function->returnType.GetTokenType() != ttVoid) 155 | { 156 | if (m_context.script_function->DoesReturnOnStack()) 157 | { 158 | allocate_parameter(m_context.script_function->returnType, "stackRetPtr"); 159 | } 160 | else 161 | { 162 | // Pointer to return value as the first arg 163 | ++argument_it; 164 | } 165 | } 166 | 167 | // is method? 168 | if (m_context.script_function->objectType != nullptr) 169 | { 170 | allocate_parameter(engine.GetDataTypeFromTypeId(m_context.script_function->objectType->GetTypeId()), "thisPtr"); 171 | } 172 | 173 | for (std::size_t i = 0; i < m_context.script_function->parameterTypes.GetLength(); ++i) 174 | { 175 | allocate_parameter( 176 | m_context.script_function->parameterTypes[i], &(m_context.script_function->parameterNames[i])[0]); 177 | } 178 | } 179 | 180 | void StackFrame::emit_debug_info() 181 | { 182 | asCScriptEngine& engine = m_context.compiler->engine(); 183 | llvm::IRBuilder<>& ir = m_context.compiler->builder().ir(); 184 | llvm::DIBuilder& di = m_context.module_builder->di_builder(); 185 | 186 | llvm::DISubprogram* sp = m_context.llvm_function->getSubprogram(); 187 | 188 | for (const auto& [stack_offset, param] : m_parameters) 189 | { 190 | llvm::DILocalVariable* local = di.createParameterVariable( 191 | sp, 192 | param.debug_name, 193 | param.argument_index, 194 | sp->getFile(), 195 | 0, 196 | m_context.module_builder->get_debug_type(param.type_id)); 197 | 198 | di.insertDeclare( 199 | param.local_alloca, 200 | local, 201 | di.createExpression(), 202 | get_debug_location(m_context, 0, sp), 203 | ir.GetInsertBlock()); 204 | } 205 | 206 | { 207 | const auto& vars = m_context.script_function->scriptData->variables; 208 | for (std::size_t i = m_context.script_function->GetParamCount(); i < vars.GetLength(); ++i) 209 | { 210 | const auto& var = vars[i]; 211 | 212 | llvm::DILocalVariable* local = di.createAutoVariable( 213 | sp, 214 | &var->name[0], 215 | sp->getFile(), 216 | 0, 217 | m_context.module_builder->get_debug_type(engine.GetTypeIdFromDataType(var->type))); 218 | 219 | di.insertDeclare( 220 | m_storage, 221 | local, 222 | di.createExpression(llvm::ArrayRef{ 223 | llvm::dwarf::DW_OP_plus_uconst, std::int64_t(total_space() - var->stackOffset) * 4}), 224 | get_debug_location(m_context, 0, sp), 225 | ir.GetInsertBlock()); 226 | } 227 | } 228 | } 229 | } // namespace asllvm::detail 230 | -------------------------------------------------------------------------------- /src/asllvm/jit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace asllvm 6 | { 7 | JitInterface::JitInterface(JitConfig flags) : 8 | m_compiler{new detail::JitCompiler{flags}, [](detail::JitCompiler* ptr) { delete ptr; }} 9 | {} 10 | 11 | int JitInterface::CompileFunction(asIScriptFunction* function, asJITFunction* output) 12 | { 13 | return m_compiler->jit_compile(function, output); 14 | } 15 | 16 | void JitInterface::ReleaseJITFunction(asJITFunction func) { return m_compiler->jit_free(func); } 17 | 18 | void JitInterface::BuildModules() { m_compiler->build_modules(); } 19 | } // namespace asllvm 20 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(tests 2 | booleans.cpp 3 | branching.cpp 4 | classmanip.cpp 5 | common.cpp 6 | enums.cpp 7 | floatmath.cpp 8 | funcdefs.cpp 9 | functions.cpp 10 | globals.cpp 11 | integermath.cpp 12 | main.cpp 13 | megatests.cpp 14 | recursion.cpp 15 | typedefs.cpp 16 | ) 17 | 18 | target_link_libraries(tests PRIVATE angelscript-llvm angelscript-addons Catch2::Catch2) 19 | target_include_directories(tests PRIVATE ../include ../3rd) 20 | 21 | include(Catch) 22 | catch_discover_tests(tests 23 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 24 | ) 25 | -------------------------------------------------------------------------------- /tests/booleans.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | TEST_CASE("int32 comparisons", "[intcmp]") 4 | { 5 | REQUIRE(run_string("int a = 15, b = 16; print(''+bool(a > b))") == "false\n"); 6 | REQUIRE(run_string("int a = 15, b = 15; print(''+bool(a > b))") == "false\n"); 7 | REQUIRE(run_string("int a = 15, b = 14; print(''+bool(a > b))") == "true\n"); 8 | 9 | REQUIRE(run_string("int a = 15, b = 16; print(''+bool(a >= b))") == "false\n"); 10 | REQUIRE(run_string("int a = 15, b = 15; print(''+bool(a >= b))") == "true\n"); 11 | REQUIRE(run_string("int a = 15, b = 14; print(''+bool(a >= b))") == "true\n"); 12 | 13 | REQUIRE(run_string("int a = 15, b = 16; print(''+bool(a < b))") == "true\n"); 14 | REQUIRE(run_string("int a = 15, b = 15; print(''+bool(a < b))") == "false\n"); 15 | REQUIRE(run_string("int a = 15, b = 14; print(''+bool(a < b))") == "false\n"); 16 | 17 | REQUIRE(run_string("int a = 15, b = 16; print(''+bool(a <= b))") == "true\n"); 18 | REQUIRE(run_string("int a = 15, b = 15; print(''+bool(a <= b))") == "true\n"); 19 | REQUIRE(run_string("int a = 15, b = 14; print(''+bool(a <= b))") == "false\n"); 20 | 21 | REQUIRE(run_string("int a = 15, b = 16; print(''+bool(a == b))") == "false\n"); 22 | REQUIRE(run_string("int a = 15, b = 15; print(''+bool(a == b))") == "true\n"); 23 | REQUIRE(run_string("int a = 15, b = 14; print(''+bool(a == b))") == "false\n"); 24 | 25 | REQUIRE(run_string("int a = 15, b = 16; print(''+bool(a != b))") == "true\n"); 26 | REQUIRE(run_string("int a = 15, b = 15; print(''+bool(a != b))") == "false\n"); 27 | REQUIRE(run_string("int a = 15, b = 14; print(''+bool(a != b))") == "true\n"); 28 | } 29 | 30 | TEST_CASE("uint32 comparisons", "[intcmp]") 31 | { 32 | REQUIRE(run_string("uint a = 15, b = 16; print(''+bool(a > b))") == "false\n"); 33 | REQUIRE(run_string("uint a = 15, b = 15; print(''+bool(a > b))") == "false\n"); 34 | REQUIRE(run_string("uint a = 15, b = 14; print(''+bool(a > b))") == "true\n"); 35 | 36 | REQUIRE(run_string("uint a = 15, b = 16; print(''+bool(a >= b))") == "false\n"); 37 | REQUIRE(run_string("uint a = 15, b = 15; print(''+bool(a >= b))") == "true\n"); 38 | REQUIRE(run_string("uint a = 15, b = 14; print(''+bool(a >= b))") == "true\n"); 39 | 40 | REQUIRE(run_string("uint a = 15, b = 16; print(''+bool(a < b))") == "true\n"); 41 | REQUIRE(run_string("uint a = 15, b = 15; print(''+bool(a < b))") == "false\n"); 42 | REQUIRE(run_string("uint a = 15, b = 14; print(''+bool(a < b))") == "false\n"); 43 | 44 | REQUIRE(run_string("uint a = 15, b = 16; print(''+bool(a <= b))") == "true\n"); 45 | REQUIRE(run_string("uint a = 15, b = 15; print(''+bool(a <= b))") == "true\n"); 46 | REQUIRE(run_string("uint a = 15, b = 14; print(''+bool(a <= b))") == "false\n"); 47 | 48 | REQUIRE(run_string("uint a = 15, b = 16; print(''+bool(a == b))") == "false\n"); 49 | REQUIRE(run_string("uint a = 15, b = 15; print(''+bool(a == b))") == "true\n"); 50 | REQUIRE(run_string("uint a = 15, b = 14; print(''+bool(a == b))") == "false\n"); 51 | 52 | REQUIRE(run_string("uint a = 15, b = 16; print(''+bool(a != b))") == "true\n"); 53 | REQUIRE(run_string("uint a = 15, b = 15; print(''+bool(a != b))") == "false\n"); 54 | REQUIRE(run_string("uint a = 15, b = 14; print(''+bool(a != b))") == "true\n"); 55 | } 56 | 57 | TEST_CASE("int64 comparisons", "[intcmp]") 58 | { 59 | REQUIRE(run_string("int64 a = 15, b = 16; print(''+bool(a > b))") == "false\n"); 60 | REQUIRE(run_string("int64 a = 15, b = 15; print(''+bool(a > b))") == "false\n"); 61 | REQUIRE(run_string("int64 a = 15, b = 14; print(''+bool(a > b))") == "true\n"); 62 | 63 | REQUIRE(run_string("int64 a = 15, b = 16; print(''+bool(a >= b))") == "false\n"); 64 | REQUIRE(run_string("int64 a = 15, b = 15; print(''+bool(a >= b))") == "true\n"); 65 | REQUIRE(run_string("int64 a = 15, b = 14; print(''+bool(a >= b))") == "true\n"); 66 | 67 | REQUIRE(run_string("int64 a = 15, b = 16; print(''+bool(a < b))") == "true\n"); 68 | REQUIRE(run_string("int64 a = 15, b = 15; print(''+bool(a < b))") == "false\n"); 69 | REQUIRE(run_string("int64 a = 15, b = 14; print(''+bool(a < b))") == "false\n"); 70 | 71 | REQUIRE(run_string("int64 a = 15, b = 16; print(''+bool(a <= b))") == "true\n"); 72 | REQUIRE(run_string("int64 a = 15, b = 15; print(''+bool(a <= b))") == "true\n"); 73 | REQUIRE(run_string("int64 a = 15, b = 14; print(''+bool(a <= b))") == "false\n"); 74 | 75 | REQUIRE(run_string("int64 a = 15, b = 16; print(''+bool(a == b))") == "false\n"); 76 | REQUIRE(run_string("int64 a = 15, b = 15; print(''+bool(a == b))") == "true\n"); 77 | REQUIRE(run_string("int64 a = 15, b = 14; print(''+bool(a == b))") == "false\n"); 78 | 79 | REQUIRE(run_string("int64 a = 15, b = 16; print(''+bool(a != b))") == "true\n"); 80 | REQUIRE(run_string("int64 a = 15, b = 15; print(''+bool(a != b))") == "false\n"); 81 | REQUIRE(run_string("int64 a = 15, b = 14; print(''+bool(a != b))") == "true\n"); 82 | } 83 | 84 | TEST_CASE("uint64 comparisons", "[intcmp]") 85 | { 86 | REQUIRE(run_string("uint64 a = 15, b = 16; print(''+bool(a > b))") == "false\n"); 87 | REQUIRE(run_string("uint64 a = 15, b = 15; print(''+bool(a > b))") == "false\n"); 88 | REQUIRE(run_string("uint64 a = 15, b = 14; print(''+bool(a > b))") == "true\n"); 89 | 90 | REQUIRE(run_string("uint64 a = 15, b = 16; print(''+bool(a >= b))") == "false\n"); 91 | REQUIRE(run_string("uint64 a = 15, b = 15; print(''+bool(a >= b))") == "true\n"); 92 | REQUIRE(run_string("uint64 a = 15, b = 14; print(''+bool(a >= b))") == "true\n"); 93 | 94 | REQUIRE(run_string("uint64 a = 15, b = 16; print(''+bool(a < b))") == "true\n"); 95 | REQUIRE(run_string("uint64 a = 15, b = 15; print(''+bool(a < b))") == "false\n"); 96 | REQUIRE(run_string("uint64 a = 15, b = 14; print(''+bool(a < b))") == "false\n"); 97 | 98 | REQUIRE(run_string("uint64 a = 15, b = 16; print(''+bool(a <= b))") == "true\n"); 99 | REQUIRE(run_string("uint64 a = 15, b = 15; print(''+bool(a <= b))") == "true\n"); 100 | REQUIRE(run_string("uint64 a = 15, b = 14; print(''+bool(a <= b))") == "false\n"); 101 | 102 | REQUIRE(run_string("uint64 a = 15, b = 16; print(''+bool(a == b))") == "false\n"); 103 | REQUIRE(run_string("uint64 a = 15, b = 15; print(''+bool(a == b))") == "true\n"); 104 | REQUIRE(run_string("uint64 a = 15, b = 14; print(''+bool(a == b))") == "false\n"); 105 | 106 | REQUIRE(run_string("uint64 a = 15, b = 16; print(''+bool(a != b))") == "true\n"); 107 | REQUIRE(run_string("uint64 a = 15, b = 15; print(''+bool(a != b))") == "false\n"); 108 | REQUIRE(run_string("uint64 a = 15, b = 14; print(''+bool(a != b))") == "true\n"); 109 | } 110 | 111 | TEST_CASE("f32 comparisons", "[fpcmp]") 112 | { 113 | REQUIRE(run_string("float a = 15, b = 16; print(''+bool(a > b))") == "false\n"); 114 | REQUIRE(run_string("float a = 15, b = 15; print(''+bool(a > b))") == "false\n"); 115 | REQUIRE(run_string("float a = 15, b = 14; print(''+bool(a > b))") == "true\n"); 116 | 117 | REQUIRE(run_string("float a = 15, b = 16; print(''+bool(a >= b))") == "false\n"); 118 | REQUIRE(run_string("float a = 15, b = 15; print(''+bool(a >= b))") == "true\n"); 119 | REQUIRE(run_string("float a = 15, b = 14; print(''+bool(a >= b))") == "true\n"); 120 | 121 | REQUIRE(run_string("float a = 15, b = 16; print(''+bool(a < b))") == "true\n"); 122 | REQUIRE(run_string("float a = 15, b = 15; print(''+bool(a < b))") == "false\n"); 123 | REQUIRE(run_string("float a = 15, b = 14; print(''+bool(a < b))") == "false\n"); 124 | 125 | REQUIRE(run_string("float a = 15, b = 16; print(''+bool(a <= b))") == "true\n"); 126 | REQUIRE(run_string("float a = 15, b = 15; print(''+bool(a <= b))") == "true\n"); 127 | REQUIRE(run_string("float a = 15, b = 14; print(''+bool(a <= b))") == "false\n"); 128 | 129 | REQUIRE(run_string("float a = 15, b = 16; print(''+bool(a == b))") == "false\n"); 130 | REQUIRE(run_string("float a = 15, b = 15; print(''+bool(a == b))") == "true\n"); 131 | REQUIRE(run_string("float a = 15, b = 14; print(''+bool(a == b))") == "false\n"); 132 | } 133 | 134 | TEST_CASE("f64 comparisons", "[fpcmp]") 135 | { 136 | REQUIRE(run_string("double a = 15, b = 16; print(''+bool(a > b))") == "false\n"); 137 | REQUIRE(run_string("double a = 15, b = 15; print(''+bool(a > b))") == "false\n"); 138 | REQUIRE(run_string("double a = 15, b = 14; print(''+bool(a > b))") == "true\n"); 139 | 140 | REQUIRE(run_string("double a = 15, b = 16; print(''+bool(a >= b))") == "false\n"); 141 | REQUIRE(run_string("double a = 15, b = 15; print(''+bool(a >= b))") == "true\n"); 142 | REQUIRE(run_string("double a = 15, b = 14; print(''+bool(a >= b))") == "true\n"); 143 | 144 | REQUIRE(run_string("double a = 15, b = 16; print(''+bool(a < b))") == "true\n"); 145 | REQUIRE(run_string("double a = 15, b = 15; print(''+bool(a < b))") == "false\n"); 146 | REQUIRE(run_string("double a = 15, b = 14; print(''+bool(a < b))") == "false\n"); 147 | 148 | REQUIRE(run_string("double a = 15, b = 16; print(''+bool(a <= b))") == "true\n"); 149 | REQUIRE(run_string("double a = 15, b = 15; print(''+bool(a <= b))") == "true\n"); 150 | REQUIRE(run_string("double a = 15, b = 14; print(''+bool(a <= b))") == "false\n"); 151 | 152 | REQUIRE(run_string("double a = 15, b = 16; print(''+bool(a == b))") == "false\n"); 153 | REQUIRE(run_string("double a = 15, b = 15; print(''+bool(a == b))") == "true\n"); 154 | REQUIRE(run_string("double a = 15, b = 14; print(''+bool(a == b))") == "false\n"); 155 | } 156 | 157 | TEST_CASE("misc. boolean logic", "[boolmisc]") 158 | { 159 | REQUIRE(run_string("bool a = false; print('' + !a);") == "true\n"); 160 | REQUIRE(run_string("bool a = true; print('' + !a);") == "false\n"); 161 | } 162 | -------------------------------------------------------------------------------- /tests/branching.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | TEST_CASE("basic integral comparisons and 'if'", "[ifcmp]") 4 | { 5 | REQUIRE(run_string("int a = 0; if (a >= 0) { print(1); }") == "1\n"); 6 | REQUIRE(run_string("int a = 0; if (a < 0) { print(0); } else { print(1); }") == "1\n"); 7 | 8 | // Emits JLowZ 9 | REQUIRE(run_string("bool a = true; if (a) { print('good'); } else { print('bad'); }") == "good\n"); 10 | 11 | // Emits JLowNZ 12 | REQUIRE(run_string("bool a = true, b = false; if (a || b) { print('good'); } else { print('bad'); }") == "good\n"); 13 | } 14 | 15 | TEST_CASE("simple 'for' looping", "[forloop]") 16 | { 17 | REQUIRE(run_string("for (int i = 0; i < 5; ++i) { print(i); }") == "0\n1\n2\n3\n4\n"); 18 | } 19 | 20 | TEST_CASE("simple 'while' looping", "[whileloop]") 21 | { 22 | REQUIRE(run_string("int i = 5; while (i != 7) { print(i); ++i; }") == "5\n6\n"); 23 | } 24 | 25 | TEST_CASE("switch statement", "[switch]") 26 | { 27 | REQUIRE(run("scripts/switch.as") == 28 | "I am Siegward of Catarina.\n" 29 | "To be honest, I'm in a bit of a pickle.\n" 30 | "Whoever it is, I'm sure I can talk some sense into them.\n" 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /tests/classmanip.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | TEST_CASE("string handling", "[str]") 4 | { 5 | REQUIRE(run("scripts/stringmanip.as", "void string_ref()") == "hello\n"); 6 | REQUIRE(run("scripts/stringmanip.as", "void string_concat()") == "hello world\n"); 7 | REQUIRE(run("scripts/stringmanip.as", "void string_concat2()") == "hello world\n"); 8 | REQUIRE(run("scripts/stringmanip.as", "void string_manylocals_concat()") == "hello world\n"); 9 | REQUIRE(run("scripts/stringmanip.as", "void string_function_reference()") == "hello world!\n"); 10 | REQUIRE(run("scripts/stringmanip.as", "void string_function_value()") == "hello world!\n"); 11 | REQUIRE(run_string(R"(print(parseInt("ABCD", 16)))") == "43981\n"); 12 | } 13 | 14 | TEST_REQUIRE("simple array logic", "[array][factory]", run("scripts/arrays/simple.as") == "123\nhi\n"); 15 | 16 | TEST_REQUIRE( 17 | "arrays with user class types", "[array][factory]", run("scripts/arrays/userclass.as") == "123\n456\n789\n"); 18 | 19 | TEST_REQUIRE( 20 | "arrays and initialization lists", 21 | "[array][factory]", 22 | run("scripts/arrays/initializationlists.as") == "123\n456\n789\nhello\nhi\n123\n"); 23 | 24 | TEST_CASE("user classes", "[userclass][simpleuserclass]") 25 | { 26 | REQUIRE(run("scripts/userclasses.as", "void test()") == "hello\n"); 27 | REQUIRE(run("scripts/userclasses.as", "void method_test()") == "hello\n123\n456\n789\n"); 28 | REQUIRE( 29 | run("scripts/userclasses.as", "void method_field_test()") 30 | == "hello\n10\n20\n30\n40\n50\n60\n70\n80\n90\n100\n"); 31 | REQUIRE(run("scripts/userclasses.as", "void handle_test()") == "hello\n123\n456\n789\n"); 32 | REQUIRE(run("scripts/userclasses.as", "void return_field_test()") == "hello\nworld\n"); 33 | 34 | // 'hello' displayed twice because of the copy construction 35 | REQUIRE( 36 | run("scripts/userclasses.as", "void pass_by_value_test()") 37 | == "hello\nhello\n10\n20\n30\n40\n50\n60\n70\n80\n90\n100\n"); 38 | } 39 | 40 | TEST_CASE("user class Vec3f", "[userclass][vec3f]") 41 | { 42 | REQUIRE(run("scripts/vec3f.as") == "150\nx: -50; y: 100; z: -50\nx: 10; y: 7.5; z: 5\n"); 43 | } 44 | 45 | TEST_CASE("devirtualization", "[devirt]") { REQUIRE(run("scripts/devirt.as") == "hello\n"); } 46 | 47 | TEST_CASE("virtual system functions", "[sysvirt]") 48 | { 49 | class Base 50 | { 51 | public: 52 | virtual void foo() { out << "Base::foo()\n"; } 53 | }; 54 | 55 | class Derived final : public Base 56 | { 57 | public: 58 | void foo() override { out << "Derived::foo()\n"; } 59 | }; 60 | 61 | EngineContext ctx(default_jit_config()); 62 | ctx.engine->RegisterObjectType("Base", sizeof(Base), asOBJ_REF | asOBJ_NOCOUNT); 63 | ctx.engine->RegisterObjectMethod("Base", "void foo()", asMETHOD(Base, foo), asCALL_THISCALL); 64 | 65 | Derived b; 66 | ctx.engine->RegisterGlobalProperty("Base b", &b); 67 | 68 | REQUIRE(run_string(ctx, "b.foo()") == "Derived::foo()\n"); 69 | } 70 | -------------------------------------------------------------------------------- /tests/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | //#define DEBUG_DISABLE_JIT 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | std::stringstream out; 12 | 13 | namespace bindings 14 | { 15 | void message_callback(const asSMessageInfo* info, [[maybe_unused]] void* param) 16 | { 17 | const char* message_type = nullptr; 18 | 19 | switch (info->type) 20 | { 21 | case asMSGTYPE_INFORMATION: 22 | { 23 | message_type = "INFO"; 24 | break; 25 | } 26 | 27 | case asMSGTYPE_WARNING: 28 | { 29 | message_type = "WARN"; 30 | break; 31 | } 32 | 33 | default: 34 | { 35 | message_type = "ERR "; 36 | break; 37 | } 38 | } 39 | 40 | std::cerr << info->section << ':' << info->row << ':' << info->col << ": " << message_type << ": " << info->message 41 | << '\n'; 42 | } 43 | 44 | void print(const std::string& message) { out << message << '\n'; } 45 | 46 | void print_int(long value) { out << value << '\n'; } 47 | void print_uint(unsigned long value) { out << value << '\n'; } 48 | void print_char(char value) { out << value; } 49 | } // namespace bindings 50 | 51 | EngineContext::EngineContext(asllvm::JitConfig config) : engine{asCreateScriptEngine()}, jit{config} 52 | { 53 | engine->SetEngineProperty(asEP_INCLUDE_JIT_INSTRUCTIONS, true); 54 | #ifndef DEBUG_DISABLE_JIT 55 | asllvm_test_check(engine->SetJITCompiler(&jit) >= 0); 56 | #endif 57 | 58 | register_interface(); 59 | } 60 | 61 | EngineContext::~EngineContext() { engine->ShutDownAndRelease(); } 62 | 63 | void EngineContext::register_interface() 64 | { 65 | RegisterStdString(engine); 66 | RegisterScriptArray(engine, true); 67 | 68 | asllvm_test_check( 69 | engine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(bindings::print), asCALL_CDECL) >= 0); 70 | asllvm_test_check( 71 | engine->RegisterGlobalFunction("void print(int64)", asFUNCTION(bindings::print_int), asCALL_CDECL) >= 0); 72 | asllvm_test_check( 73 | engine->RegisterGlobalFunction("void print(uint64)", asFUNCTION(bindings::print_uint), asCALL_CDECL) >= 0); 74 | asllvm_test_check( 75 | engine->RegisterGlobalFunction("void putchar(uint8)", asFUNCTION(bindings::print_char), asCALL_CDECL) >= 0); 76 | 77 | asllvm_test_check(engine->SetMessageCallback(asFUNCTION(bindings::message_callback), nullptr, asCALL_CDECL) >= 0); 78 | } 79 | 80 | asIScriptModule& EngineContext::build(const char* name, const char* script_path) 81 | { 82 | CScriptBuilder builder; 83 | asllvm_test_check(builder.StartNewModule(engine, name) >= 0); 84 | asllvm_test_check(builder.AddSectionFromFile(script_path) >= 0); 85 | asllvm_test_check(builder.BuildModule() >= 0); 86 | 87 | return *engine->GetModule(name); 88 | } 89 | 90 | void EngineContext::prepare_execution() 91 | { 92 | #ifndef DEBUG_DISABLE_JIT 93 | jit.BuildModules(); 94 | #endif 95 | } 96 | 97 | void EngineContext::run(asIScriptModule& module, const char* entry_point) 98 | { 99 | prepare_execution(); 100 | 101 | asIScriptFunction* function = module.GetFunctionByDecl(entry_point); 102 | asllvm_test_check(function != nullptr); 103 | 104 | asIScriptContext* context = engine->CreateContext(); 105 | asllvm_test_check(context->Prepare(function) >= 0); 106 | 107 | asllvm_test_check(context->Execute() == asEXECUTION_FINISHED); 108 | 109 | context->Release(); 110 | } 111 | 112 | asllvm::JitConfig default_jit_config() 113 | { 114 | asllvm::JitConfig config; 115 | config.verbose = true; 116 | config.allow_llvm_optimizations = false; 117 | return config; 118 | } 119 | 120 | std::string run(const char* path, const char* entry) 121 | { 122 | EngineContext context(default_jit_config()); 123 | return run(context, path, entry); 124 | } 125 | 126 | std::string run(EngineContext& context, const char* path, const char* entry) 127 | { 128 | out = {}; 129 | asIScriptModule& module = context.build("build", path); 130 | context.run(module, entry); 131 | return out.str(); 132 | } 133 | 134 | std::string run_string(const char* str) 135 | { 136 | EngineContext context(default_jit_config()); 137 | return run_string(context, str); 138 | } 139 | 140 | std::string run_string(EngineContext& context, const char* str) 141 | { 142 | out = {}; 143 | 144 | CScriptBuilder builder; 145 | builder.StartNewModule(context.engine, "build"); 146 | builder.AddSectionFromMemory("str", (std::string("void main() {") + str + ";}").c_str()); 147 | builder.BuildModule(); 148 | 149 | context.run(*context.engine->GetModule("build"), "void main()"); 150 | 151 | return out.str(); 152 | } 153 | -------------------------------------------------------------------------------- /tests/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define CATCH_CONFIG_ENABLE_BENCHMARKING 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define TEST_REQUIRE(name, tag, cond) \ 13 | TEST_CASE(name, tag) { REQUIRE(cond); } 14 | 15 | #define asllvm_test_check(x) \ 16 | if (!(x)) \ 17 | { \ 18 | throw std::runtime_error{"check failed: " #x}; \ 19 | } \ 20 | (void)0 21 | 22 | extern std::stringstream out; 23 | 24 | struct EngineContext 25 | { 26 | EngineContext(asllvm::JitConfig config); 27 | 28 | ~EngineContext(); 29 | 30 | void register_interface(); 31 | 32 | asIScriptModule& build(const char* name, const char* script_path); 33 | 34 | void prepare_execution(); 35 | 36 | void run(asIScriptModule& module, const char* entry_point); 37 | 38 | asIScriptEngine* engine; 39 | asllvm::JitInterface jit; 40 | }; 41 | 42 | asllvm::JitConfig default_jit_config(); 43 | 44 | std::string run(const char* path, const char* entry = "void main()"); 45 | std::string run(EngineContext& context, const char* path, const char* entry = "void main()"); 46 | std::string run_string(const char* str); 47 | std::string run_string(EngineContext& context, const char* str); 48 | -------------------------------------------------------------------------------- /tests/enums.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | // Enums don't really seem to require any bytecode support, but keep this in here just in case. 4 | 5 | TEST_CASE("enums", "[enums]") { REQUIRE(run("scripts/enums.as") == "11\n"); } 6 | -------------------------------------------------------------------------------- /tests/floatmath.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | TEST_CASE("32-bit float math", "[floatmath32]") 4 | { 5 | REQUIRE(run_string("float a = 3.141f; print(''+a);") == "3.141\n"); 6 | 7 | REQUIRE(run_string("float a = 1.0f, b = 2.0f; print(''+(a + b));") == "3\n"); 8 | REQUIRE(run_string("float a = 1.0f, b = 2.0f; print(''+(a - b));") == "-1\n"); 9 | REQUIRE(run_string("float a = 5.0f, b = 2.0f; print(''+(a * b));") == "10\n"); 10 | REQUIRE(run_string("float a = 5.0f, b = 2.0f; print(''+(a / b));") == "2.5\n"); 11 | REQUIRE(run_string("float a = 10.0f, b = 6.0f; print(''+(a % b));") == "4\n"); 12 | 13 | REQUIRE(run_string("float a = 10.0f; print(''+ (-a));") == "-10\n"); 14 | 15 | REQUIRE(run_string("float a = 1.0f; a += 2.0f; print(''+a);") == "3\n"); 16 | REQUIRE(run_string("float a = 1.0f; a -= 2.0f; print(''+a);") == "-1\n"); 17 | REQUIRE(run_string("float a = 5.0f; a *= 2.0f; print(''+a);") == "10\n"); 18 | REQUIRE(run_string("float a = 5.0f; a /= 2.0f; print(''+a);") == "2.5\n"); 19 | REQUIRE(run_string("float a = 10.0f; a %= 6.0f; print(''+a);") == "4\n"); 20 | 21 | REQUIRE(run_string("float a = 10.0f; print(''+ ++a);") == "11\n"); 22 | REQUIRE(run_string("float a = 10.0f; print(''+ --a);") == "9\n"); 23 | } 24 | 25 | TEST_CASE("64-bit float math", "[floatmath32]") 26 | { 27 | REQUIRE(run_string("double a = 3.141; print(''+a);") == "3.141\n"); 28 | 29 | REQUIRE(run_string("double a = 1.0, b = 2.0; print(''+(a + b));") == "3\n"); 30 | REQUIRE(run_string("double a = 1.0, b = 2.0; print(''+(a - b));") == "-1\n"); 31 | REQUIRE(run_string("double a = 5.0, b = 2.0; print(''+(a * b));") == "10\n"); 32 | REQUIRE(run_string("double a = 5.0, b = 2.0; print(''+(a / b));") == "2.5\n"); 33 | REQUIRE(run_string("double a = 10.0, b = 6.0 ; print(''+(a % b));") == "4\n"); 34 | 35 | REQUIRE(run_string("double a = 10.0; print(''+ (-a));") == "-10\n"); 36 | 37 | REQUIRE(run_string("double a = 1.0; a += 2.0; print(''+a);") == "3\n"); 38 | REQUIRE(run_string("double a = 1.0; a -= 2.0; print(''+a);") == "-1\n"); 39 | REQUIRE(run_string("double a = 5.0; a *= 2.0; print(''+a);") == "10\n"); 40 | REQUIRE(run_string("double a = 5.0; a /= 2.0; print(''+a);") == "2.5\n"); 41 | REQUIRE(run_string("double a = 10.0; a %= 6.0; print(''+a);") == "4\n"); 42 | 43 | REQUIRE(run_string("double a = 10.0; print(''+ ++a);") == "11\n"); 44 | REQUIRE(run_string("double a = 10.0; print(''+ --a);") == "9\n"); 45 | } 46 | 47 | TEST_CASE("Floating-point to floating-point conversions", "[castfpfp]") 48 | { 49 | REQUIRE(run_string("double a = 3.141; print(''+float(a))") == "3.141\n"); 50 | REQUIRE(run_string("float a = 3.141; print(''+double(a))") == "3.141\n"); 51 | } 52 | 53 | TEST_CASE("Floating-point <-> integral conversions", "[castfpint]") 54 | { 55 | REQUIRE(run_string("int a = -123; print(''+float(a))") == "-123\n"); 56 | REQUIRE(run_string("float a = -123.456; print(''+int(a))") == "-123\n"); 57 | REQUIRE(run_string("uint a = 123; print(''+float(a))") == "123\n"); 58 | REQUIRE(run_string("float a = 123.456; print(''+uint(a))") == "123\n"); 59 | 60 | REQUIRE(run_string("int a = -123; print(''+double(a))") == "-123\n"); 61 | REQUIRE(run_string("double a = -123.456; print(''+int(a))") == "-123\n"); 62 | REQUIRE(run_string("uint a = 123; print(''+double(a))") == "123\n"); 63 | REQUIRE(run_string("double a = 123.456; print(''+uint(a))") == "123\n"); 64 | 65 | REQUIRE(run_string("float a = -123.456; print(''+int64(a))") == "-123\n"); 66 | REQUIRE(run_string("double a = -123.456; print(''+int64(a))") == "-123\n"); 67 | REQUIRE(run_string("float a = 123.456; print(''+uint64(a))") == "123\n"); 68 | REQUIRE(run_string("double a = 123.456; print(''+uint64(a))") == "123\n"); 69 | 70 | REQUIRE(run_string("int64 a = -123; print(''+float(a))") == "-123\n"); 71 | REQUIRE(run_string("uint64 a = 123; print(''+float(a))") == "123\n"); 72 | REQUIRE(run_string("int64 a = -123; print(''+double(a))") == "-123\n"); 73 | REQUIRE(run_string("uint64 a = 123; print(''+double(a))") == "123\n"); 74 | } 75 | -------------------------------------------------------------------------------- /tests/funcdefs.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | /* 3 | TEST_CASE("function pointer calls", "[funcdef]") 4 | { 5 | REQUIRE(run("scripts/funcdefs.as", "void test_system()") == "hello\n"); 6 | REQUIRE(run("scripts/funcdefs.as", "void test_script()") == "hello\n"); 7 | } 8 | */ 9 | -------------------------------------------------------------------------------- /tests/functions.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | TEST_CASE("simple parameterized function", "[params]") { REQUIRE(run("scripts/functions.as") == "10000\n"); } 4 | 5 | TEST_CASE("references to primitives in parameters", "[refparams]") 6 | { 7 | REQUIRE(run("scripts/refprimitives.as") == "10\n"); 8 | } 9 | 10 | TEST_CASE("shared functions", "[shared][sharedfuncs]") 11 | { 12 | EngineContext context(default_jit_config()); 13 | 14 | out = {}; 15 | 16 | asIScriptModule& module_a = context.build("a", "scripts/sharedfuncs.as"); 17 | asIScriptModule& module_b = context.build("b", "scripts/sharedfuncs.as"); 18 | 19 | context.prepare_execution(); 20 | 21 | asIScriptFunction* entry_a = module_a.GetFunctionByDecl("void main()"); 22 | asIScriptFunction* entry_b = module_b.GetFunctionByDecl("void main()"); 23 | 24 | asIScriptContext* script_context = context.engine->CreateContext(); 25 | 26 | const auto run = [&](asIScriptFunction* func) { 27 | asllvm_test_check(script_context->Prepare(func) >= 0); 28 | asllvm_test_check(script_context->Execute() == asEXECUTION_FINISHED); 29 | }; 30 | 31 | run(entry_a); 32 | run(entry_b); 33 | 34 | REQUIRE(out.str() == "10\n10\n"); 35 | } 36 | -------------------------------------------------------------------------------- /tests/globals.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | TEST_CASE("globals", "[globals]") 4 | { 5 | REQUIRE(run("scripts/globals.as", "void assign_read()") == "123\n123\n123\n123\n"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/integermath.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | // Note that _some_ of the 8-bit and 16-bit arithmetic checks are somewhat redundant: operations over these types 4 | // usually get promoted to 32-bit. Checking for this potentially helps detecting bugs related to sign extension and 5 | // such, though. 6 | 7 | TEST_CASE("8-bit signed math", "[signedmath8]") 8 | { 9 | REQUIRE(run_string("int8 a = 1, b = -2; print(a + b)") == "-1\n"); 10 | REQUIRE(run_string("int8 a = 10, b = 20; print(a - b)") == "-10\n"); 11 | REQUIRE(run_string("int8 a = 10, b = -5; print(a * b)") == "-50\n"); 12 | REQUIRE(run_string("int8 a = 10, b = -2; print(a / b)") == "-5\n"); 13 | REQUIRE(run_string("int8 a = 7, b = 4; print(a % b)") == "3\n"); 14 | REQUIRE(run_string("int8 a = 10; print(++a)") == "11\n"); 15 | REQUIRE(run_string("int8 a = 10; print(--a)") == "9\n"); 16 | } 17 | 18 | TEST_CASE("16-bit signed math", "[signedmath16]") 19 | { 20 | REQUIRE(run_string("int16 a = 1, b = -2; print(a + b)") == "-1\n"); 21 | REQUIRE(run_string("int16 a = 10, b = 20; print(a - b)") == "-10\n"); 22 | REQUIRE(run_string("int16 a = 10, b = -5; print(a * b)") == "-50\n"); 23 | REQUIRE(run_string("int16 a = 10, b = -2; print(a / b)") == "-5\n"); 24 | REQUIRE(run_string("int16 a = 7, b = 4; print(a % b)") == "3\n"); 25 | REQUIRE(run_string("int16 a = 10; print(++a)") == "11\n"); 26 | REQUIRE(run_string("int16 a = 10; print(--a)") == "9\n"); 27 | } 28 | 29 | TEST_CASE("32-bit signed math", "[signedmath32]") 30 | { 31 | REQUIRE(run_string("int a = 1, b = -2; print(a + b)") == "-1\n"); 32 | REQUIRE(run_string("int a = 10, b = 20; print(a - b)") == "-10\n"); 33 | REQUIRE(run_string("int a = 10, b = -5; print(a * b)") == "-50\n"); 34 | REQUIRE(run_string("int a = 10, b = -2; print(a / b)") == "-5\n"); 35 | REQUIRE(run_string("int a = 7, b = 4; print(a % b)") == "3\n"); 36 | 37 | REQUIRE(run_string("int a = 10; print(-a)") == "-10\n"); 38 | 39 | REQUIRE(run_string("int a = 10; print(++a)") == "11\n"); 40 | REQUIRE(run_string("int a = 10; print(--a)") == "9\n"); 41 | } 42 | 43 | TEST_CASE("64-bit signed math", "[signedmath64]") 44 | { 45 | REQUIRE(run_string("int64 a = 1, b = -2; print(a + b)") == "-1\n"); 46 | REQUIRE(run_string("int64 a = 10, b = 20; print(a - b)") == "-10\n"); 47 | REQUIRE(run_string("int64 a = 10, b = -5; print(a * b)") == "-50\n"); 48 | REQUIRE(run_string("int64 a = 10, b = -2; print(a / b)") == "-5\n"); 49 | REQUIRE(run_string("int64 a = 7, b = 4; print(a % b)") == "3\n"); 50 | 51 | REQUIRE(run_string("int64 a = 10; print(-a)") == "-10\n"); 52 | 53 | REQUIRE(run_string("int64 a = 10; print(++a)") == "11\n"); 54 | REQUIRE(run_string("int64 a = 10; print(--a)") == "9\n"); 55 | } 56 | 57 | TEST_CASE("unsigned overflow logic", "[unsignedmathoverflow]") 58 | { 59 | REQUIRE(run_string("uint8 a = 1, b = uint8(-2); print(a + b)") == "255\n"); 60 | REQUIRE(run_string("uint16 a = 1, b = uint16(-2); print(a + b)") == "65535\n"); 61 | REQUIRE(run_string("uint32 a = 1, b = uint32(-2); print(a + b)") == "4294967295\n"); 62 | REQUIRE(run_string("uint64 a = 1, b = uint64(-2); print(a + b)") == "18446744073709551615\n"); 63 | } 64 | 65 | TEST_CASE("32-bit unsigned division math", "[unsignedmathdiv32]") 66 | { 67 | REQUIRE(run_string("uint32 a = 10, b = 4; print(a / b)") == "2\n"); 68 | REQUIRE(run_string("uint32 a = 10, b = 4; print(a % b)") == "2\n"); 69 | } 70 | 71 | TEST_CASE("64-bit unsigned division math", "[unsignedmathdiv64]") 72 | { 73 | REQUIRE(run_string("uint64 a = 10, b = 4; print(a / b)") == "2\n"); 74 | REQUIRE(run_string("uint64 a = 10, b = 4; print(a % b)") == "2\n"); 75 | } 76 | 77 | TEST_CASE("32-bit bitwise logic", "[bitwise32]") 78 | { 79 | REQUIRE(run_string("int32 a = 4354352, b = 1213516; print(a & b)") == "131072\n"); 80 | REQUIRE(run_string("int32 a = 4354352, b = 1213516; print(a | b)") == "5436796\n"); 81 | REQUIRE(run_string("int32 a = 4354352, b = 1213516; print(a ^ b)") == "5305724\n"); 82 | REQUIRE(run_string("int32 a = 4354352, b = 2; print(a << b)") == "17417408\n"); 83 | REQUIRE(run_string("int32 a = 4354352, b = 2; print(a >> b)") == "1088588\n"); 84 | REQUIRE(run_string("int32 a = -4354352, b = 2; print(a >> b)") == "1072653236\n"); 85 | REQUIRE(run_string("int32 a = 4354352, b = 2; print(a >>> b)") == "1088588\n"); 86 | REQUIRE(run_string("int32 a = -4354352, b = 2; print(a >>> b)") == "-1088588\n"); 87 | REQUIRE(run_string("int32 a = 0xF0F0F0F0; print(~a)") == "252645135\n"); 88 | } 89 | 90 | TEST_CASE("64-bit bitwise logic", "[bitwise64]") 91 | { 92 | REQUIRE(run_string("int64 a = 4354352, b = 1213516; print(a & b)") == "131072\n"); 93 | REQUIRE(run_string("int64 a = 4354352, b = 1213516; print(a | b)") == "5436796\n"); 94 | REQUIRE(run_string("int64 a = 4354352, b = 1213516; print(a ^ b)") == "5305724\n"); 95 | REQUIRE(run_string("int64 a = 4354352, b = 2; print(a << b)") == "17417408\n"); 96 | REQUIRE(run_string("int64 a = 4354352, b = 2; print(a >> b)") == "1088588\n"); 97 | REQUIRE(run_string("int64 a = -4354352, b = 2; print(a >> b)") == "4611686018426299316\n"); 98 | REQUIRE(run_string("int64 a = 4354352, b = 2; print(a >>> b)") == "1088588\n"); 99 | REQUIRE(run_string("int64 a = -4354352, b = 2; print(a >>> b)") == "-1088588\n"); 100 | REQUIRE(run_string("int64 a = 0xF0F0F0F0F0F0F0F0; print(~a)") == "1085102592571150095\n"); 101 | } 102 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #define CATCH_CONFIG_ENABLE_BENCHMARKING 3 | #include 4 | -------------------------------------------------------------------------------- /tests/megatests.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | TEST_CASE("brainf**k interpreter", "[megatest][bf]") { REQUIRE(run("scripts/bfint.as") == "hello world"); } 4 | -------------------------------------------------------------------------------- /tests/recursion.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | TEST_CASE("recursive fibonacci", "[fib]") 4 | { 5 | EngineContext context(default_jit_config()); 6 | 7 | out = {}; 8 | 9 | asIScriptModule& module = context.build("build", "scripts/fib.as"); 10 | context.prepare_execution(); 11 | 12 | asIScriptFunction* fib = module.GetFunctionByDecl("int fib(int)"); 13 | asllvm_test_check(fib != nullptr); 14 | 15 | asIScriptContext* script_context = context.engine->CreateContext(); 16 | 17 | const auto run_fib = [&](int i) -> int { 18 | asllvm_test_check(script_context->Prepare(fib) >= 0); 19 | asllvm_test_check(script_context->SetArgDWord(0, i) >= 0); 20 | asllvm_test_check(script_context->Execute() == asEXECUTION_FINISHED); 21 | return script_context->GetReturnDWord(); 22 | }; 23 | 24 | REQUIRE(run_fib(10) == 55); 25 | REQUIRE(run_fib(20) == 6765); 26 | REQUIRE(run_fib(25) == 75025); 27 | REQUIRE(run_fib(35) == 9227465); 28 | } 29 | -------------------------------------------------------------------------------- /tests/scripts/arrays/initializationlists.as: -------------------------------------------------------------------------------- 1 | class Foo { 2 | Foo() {} 3 | Foo(int value) { m_value = value; } 4 | 5 | int m_value; 6 | }; 7 | 8 | void main() 9 | { 10 | int[] ints = {123, 456, 789}; 11 | string[] strings = {"hello", "hi"}; 12 | Foo[] foos = {Foo(123)}; 13 | 14 | for (uint i = 0; i < ints.length(); ++i) 15 | { 16 | print(ints[i]); 17 | } 18 | 19 | for (uint i = 0; i < strings.length(); ++i) 20 | { 21 | print(strings[i]); 22 | } 23 | 24 | for (uint i = 0; i < foos.length(); ++i) 25 | { 26 | print(foos[i].m_value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/scripts/arrays/simple.as: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | // array is a template type. Test having multiple types to make sure that, well, they work, but also importantly 4 | // that the multiple instanciations of the same template type does not cause issues. 5 | 6 | int[] ints; 7 | string[] strings; 8 | 9 | ints.insertLast(123); 10 | strings.insertLast("hi"); 11 | 12 | print(ints[0]); 13 | print(strings[0]); 14 | } 15 | -------------------------------------------------------------------------------- /tests/scripts/arrays/userclass.as: -------------------------------------------------------------------------------- 1 | class Foo 2 | { 3 | Foo() {} 4 | Foo(int a) { m_a = a; } 5 | 6 | int m_a; 7 | }; 8 | 9 | void main() 10 | { 11 | Foo[] foos; 12 | foos.insertLast(Foo(123)); 13 | foos.insertLast(Foo(456)); 14 | foos.insertLast(Foo(789)); 15 | 16 | for (uint i = 0; i < foos.length(); ++i) 17 | { 18 | print(foos[i].m_a); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/scripts/bfint.as: -------------------------------------------------------------------------------- 1 | void bf(const string &in source) 2 | { 3 | uint8[] memory(30000); 4 | 5 | uint32 tape_pointer = 0; 6 | uint32 instruction_pointer = 0; 7 | 8 | const uint32 source_size = source.length(); 9 | 10 | while (true) 11 | { 12 | if (instruction_pointer == source_size) 13 | { 14 | break; 15 | } 16 | 17 | switch (source[instruction_pointer]) 18 | { 19 | case 0x2B: // + 20 | { 21 | ++memory[tape_pointer]; 22 | break; 23 | } 24 | 25 | case 0x2D: // - 26 | { 27 | --memory[tape_pointer]; 28 | break; 29 | } 30 | 31 | case 0x2E: // . 32 | { 33 | putchar(memory[tape_pointer]); 34 | break; 35 | } 36 | 37 | case 0x5B: // [ 38 | { 39 | if (memory[tape_pointer] != 0) 40 | { 41 | break; 42 | } 43 | 44 | int depth = 0; 45 | while (true) 46 | { 47 | const auto ins = source[instruction_pointer]; 48 | if (ins == 0x5B) { ++depth; } 49 | else if (ins == 0x5D) { --depth; } 50 | 51 | if (depth == 0) 52 | { 53 | break; 54 | } 55 | 56 | ++instruction_pointer; 57 | } 58 | 59 | break; 60 | } 61 | 62 | case 0x5D: // ] 63 | { 64 | if (memory[tape_pointer] == 0) 65 | { 66 | break; 67 | } 68 | 69 | int depth = 0; 70 | while (true) 71 | { 72 | const auto ins = source[instruction_pointer]; 73 | if (ins == 0x5B) { ++depth; } 74 | else if (ins == 0x5D) { --depth; } 75 | 76 | if (depth == 0) 77 | { 78 | break; 79 | } 80 | 81 | --instruction_pointer; 82 | } 83 | 84 | break; 85 | } 86 | 87 | case 0x3C: // < 88 | { 89 | --tape_pointer; 90 | break; 91 | } 92 | 93 | case 0x3E: // > 94 | { 95 | ++tape_pointer; 96 | break; 97 | } 98 | } 99 | 100 | ++instruction_pointer; 101 | } 102 | } 103 | 104 | void main() 105 | { 106 | bf("+[-[<<[+[--->]-[<<<]]]>>>-]>-.---.>..>.<<<<-.<+.>>>>>.>.<<.<-."); 107 | } 108 | -------------------------------------------------------------------------------- /tests/scripts/devirt.as: -------------------------------------------------------------------------------- 1 | class A 2 | { 3 | void foo() final 4 | { 5 | print("hello"); 6 | } 7 | }; 8 | 9 | class B : A {}; 10 | 11 | void main() 12 | { 13 | B().foo(); 14 | } 15 | -------------------------------------------------------------------------------- /tests/scripts/enums.as: -------------------------------------------------------------------------------- 1 | namespace MyEnum 2 | { 3 | enum MyEnum 4 | { 5 | A = 10, 6 | B, 7 | C 8 | }; 9 | } 10 | 11 | void main() 12 | { 13 | MyEnum::MyEnum e = MyEnum::B; 14 | print(e); 15 | } 16 | -------------------------------------------------------------------------------- /tests/scripts/fib.as: -------------------------------------------------------------------------------- 1 | int fib(int n) 2 | { 3 | if (n < 2) 4 | { 5 | return n; 6 | } 7 | 8 | return fib(n-1) + fib(n-2); 9 | } 10 | -------------------------------------------------------------------------------- /tests/scripts/funcdefs.as: -------------------------------------------------------------------------------- 1 | funcdef void SOME_FUNCDEF(const string &in); 2 | 3 | void print_proxy_test(const string &in str) 4 | { 5 | print(str); 6 | } 7 | 8 | void test_system() 9 | { 10 | SOME_FUNCDEF@ printer = @print; 11 | printer("hello"); 12 | } 13 | 14 | void test_script() 15 | { 16 | SOME_FUNCDEF@ printer = @print_proxy_test; 17 | printer("hello"); 18 | } 19 | -------------------------------------------------------------------------------- /tests/scripts/functions.as: -------------------------------------------------------------------------------- 1 | // Basic function stuff - useful for testing source-level debugging. 2 | 3 | void main() 4 | { 5 | print(/*'' + */calc(10, 100)); 6 | } 7 | 8 | int calc(int64 a, int b) 9 | { 10 | a *= 10; 11 | int v = a; 12 | v *= b; 13 | return v; 14 | } 15 | -------------------------------------------------------------------------------- /tests/scripts/globals.as: -------------------------------------------------------------------------------- 1 | int8 g8; 2 | int16 g16; 3 | int g32; 4 | int64 g64; 5 | 6 | void assign_read() 7 | { 8 | g8 = 123; 9 | g16 = 123; 10 | g32 = 123; 11 | g64 = 123; 12 | 13 | print(g8); 14 | print(g16); 15 | print(g32); 16 | print(g64); 17 | } 18 | -------------------------------------------------------------------------------- /tests/scripts/refprimitives.as: -------------------------------------------------------------------------------- 1 | void calc(int &in a, int &out b) 2 | { 3 | b = a; 4 | } 5 | 6 | void main() 7 | { 8 | int foo = 123; 9 | calc(10, foo); 10 | print(foo); 11 | } 12 | -------------------------------------------------------------------------------- /tests/scripts/sharedfuncs.as: -------------------------------------------------------------------------------- 1 | shared int calc(int a, int b) 2 | { 3 | return a * b; 4 | } 5 | 6 | void main() 7 | { 8 | print(calc(5, 2)); 9 | } 10 | -------------------------------------------------------------------------------- /tests/scripts/stringmanip.as: -------------------------------------------------------------------------------- 1 | void string_ref() 2 | { 3 | string local_string = "hello"; 4 | print(local_string); 5 | } 6 | 7 | void string_concat() 8 | { 9 | string a = "hello ", b = "world"; 10 | print(a + b); 11 | } 12 | 13 | void string_concat2() 14 | { 15 | string a = "hello "; 16 | a += "world"; 17 | print(a); 18 | } 19 | 20 | void string_manylocals_concat() 21 | { 22 | string a = "h", b = "e", c = "l", d = "l", e = "o", f = " "; 23 | print(a + b + c + d + e + f + "world"); 24 | } 25 | 26 | void takes_string_reference(const string &in a, const string &in b, const string &in c) 27 | { 28 | print(a + b + c); 29 | } 30 | 31 | void string_function_reference() 32 | { 33 | takes_string_reference("hello", " world", "!"); 34 | } 35 | 36 | void takes_string_value(string a, string b, string c) 37 | { 38 | print(a + b + c); 39 | } 40 | 41 | void string_function_value() 42 | { 43 | takes_string_value("hello", " world", "!"); 44 | } 45 | -------------------------------------------------------------------------------- /tests/scripts/switch.as: -------------------------------------------------------------------------------- 1 | void switch_test(int i) 2 | { 3 | switch (i) 4 | { 5 | case 0: 6 | { 7 | print("Hmm... Mmmmmm... Hmm... Mmm..."); 8 | break; 9 | } 10 | 11 | case 1: 12 | { 13 | print("Pardon me, I was absorbed in thought."); 14 | break; 15 | } 16 | 17 | case 2: 18 | { 19 | print("I am Siegward of Catarina."); 20 | // Fallthrough 21 | } 22 | 23 | case 3: 24 | { 25 | print("To be honest, I'm in a bit of a pickle."); 26 | break; 27 | } 28 | 29 | case 4: 30 | { 31 | print("Have you ever walked near a white birch, only to be struck by a great arrow?"); 32 | break; 33 | } 34 | 35 | case 5: 36 | { 37 | print("Well, if I'm not mistaken, they come from this tower."); 38 | break; 39 | } 40 | 41 | default: 42 | { 43 | print("Whoever it is, I'm sure I can talk some sense into them."); 44 | } 45 | } 46 | } 47 | 48 | void main() 49 | { 50 | switch_test(2); 51 | switch_test(30); 52 | } 53 | -------------------------------------------------------------------------------- /tests/scripts/typedefs.as: -------------------------------------------------------------------------------- 1 | // We only test primitive typedefs because only those are supported: 2 | // https://www.angelcode.com/angelscript/sdk/docs/manual/doc_global_typedef.html 3 | 4 | typedef float f32; 5 | 6 | void main() 7 | { 8 | f32 v = 3.141; 9 | print('' + f32(v)); 10 | } 11 | -------------------------------------------------------------------------------- /tests/scripts/userclasses.as: -------------------------------------------------------------------------------- 1 | class Foo 2 | { 3 | // Make sure the constructeur is called correctly, and that stuff is correctly executed inside 4 | Foo() 5 | { 6 | print("hello"); 7 | } 8 | 9 | // Test virtual function calls and parameters inside of methods 10 | void foo(int a, int b, int c) 11 | { 12 | print(a); 13 | print(b); 14 | print(c); 15 | } 16 | 17 | // Somewhat random field organization and writes to test for potential writes overlapping with other members. 18 | void use_field() 19 | { 20 | m2 = 20; 21 | m3 = 30; 22 | m5 = 50; 23 | m7 = 70; 24 | m6 = 60; 25 | m8 = 80; 26 | m10 = 100; 27 | m9 = 90; 28 | m4 = 40; 29 | m1 = 10; 30 | 31 | print(m1); 32 | print(m2); 33 | print(m3); 34 | print(m4); 35 | print(m5); 36 | print(m6); 37 | print(m7); 38 | print(m8); 39 | print(m9); 40 | print(m10); 41 | } 42 | 43 | string return_string_field() 44 | { 45 | str_field = "world"; 46 | return str_field; 47 | } 48 | 49 | int8 m1, m2; 50 | int16 m3; 51 | int32 m4; 52 | int64 m5; 53 | int16 m6; 54 | int8 m7; 55 | int16 m8; 56 | int8 m9; 57 | int64 m10; 58 | 59 | string str_field; 60 | }; 61 | 62 | void test() 63 | { 64 | Foo f; 65 | } 66 | 67 | void method_test() 68 | { 69 | Foo f; 70 | f.foo(123, 456, 789); 71 | } 72 | 73 | void method_field_test() 74 | { 75 | Foo f; 76 | f.use_field(); 77 | } 78 | 79 | void return_field_test() 80 | { 81 | Foo f; 82 | print(f.return_string_field()); 83 | } 84 | 85 | void handle_test() 86 | { 87 | Foo f1; 88 | Foo@ f2 = @f1; 89 | f2.foo(123, 456, 789); 90 | } 91 | 92 | void take_by_value(Foo foo) 93 | { 94 | foo.use_field(); 95 | } 96 | 97 | void pass_by_value_test() 98 | { 99 | Foo f; 100 | take_by_value(f); 101 | } 102 | -------------------------------------------------------------------------------- /tests/scripts/vec3f.as: -------------------------------------------------------------------------------- 1 | final class Vec3f 2 | { 3 | float x; 4 | float y; 5 | float z; 6 | 7 | Vec3f() 8 | { 9 | x = 0; 10 | y = 0; 11 | z = 0; 12 | } 13 | 14 | Vec3f(float _x, float _y, float _z) 15 | { 16 | x = _x; 17 | y = _y; 18 | z = _z; 19 | } 20 | 21 | Vec3f opAdd(const Vec3f&in oof) const { return Vec3f(x + oof.x, y + oof.y, z + oof.z); } 22 | 23 | Vec3f opAdd(float oof) const { return Vec3f(x + oof, y + oof, z + oof); } 24 | 25 | void opAddAssign(const Vec3f&in oof) { x += oof.x; y += oof.y; z += oof.z; } 26 | 27 | void opAddAssign(float oof) { x += oof; y += oof; z += oof; } 28 | 29 | Vec3f opSub(const Vec3f&in oof) const { return Vec3f(x - oof.x, y - oof.y, z - oof.z); } 30 | 31 | Vec3f opSub(float oof) const { return Vec3f(x - oof, y - oof, z - oof); } 32 | 33 | void opSubAssign(const Vec3f&in oof) { x -= oof.x; y -= oof.y; z -= oof.z; } 34 | 35 | Vec3f opMul(const Vec3f&in oof) { return Vec3f(x * oof.x, y * oof.y, z * oof.z); } 36 | 37 | Vec3f opMul(float oof) const { return Vec3f(x * oof, y * oof, z * oof); } 38 | 39 | void opMulAssign(float oof) { x *= oof; y *= oof; z *= oof; } 40 | 41 | Vec3f opDiv(const Vec3f&in oof) const { return Vec3f(x / oof.x, y / oof.y, z / oof.z); } 42 | 43 | Vec3f opDiv(float oof) { return Vec3f(x / oof, y / oof, z / oof); } 44 | 45 | void opDivAssign(float oof) { x /= oof; y /= oof; z /= oof; } 46 | 47 | void opAssign(const Vec3f &in oof){ x=oof.x;y=oof.y;z=oof.z; } 48 | 49 | Vec3f Lerp(const Vec3f&in desired, float t) 50 | { 51 | return Vec3f((((1 - t) * this.x) + (t * desired.x)), (((1 - t) * this.y) + (t * desired.y)), (((1 - t) * this.z) + (t * desired.z))); 52 | } 53 | 54 | void Print() 55 | { 56 | print("x: "+x+"; y: "+y+"; z: "+z); 57 | } 58 | 59 | string IntString() 60 | { 61 | return int(x)+", "+int(y)+", "+int(z); 62 | } 63 | 64 | string FloatString() 65 | { 66 | return x+", "+y+", "+z; 67 | } 68 | } 69 | 70 | float DotProduct(const Vec3f&in v1, const Vec3f&in v2) 71 | { 72 | return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; 73 | } 74 | 75 | Vec3f CrossProduct(const Vec3f&in v1, const Vec3f&in v2) 76 | { 77 | return Vec3f(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); 78 | } 79 | 80 | void main() 81 | { 82 | Vec3f v1(10.0f, 10.0f, 10.0f); 83 | Vec3f v2(10.0f, 5.0f, 0.0f); 84 | print('' + DotProduct(v1, v2)); 85 | CrossProduct(v1, v2).Print(); 86 | v1.Lerp(v2, 0.5).Print(); 87 | } 88 | -------------------------------------------------------------------------------- /tests/typedefs.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | // Typedefs don't seem to require any bytecode support, but keep this just in case. 4 | 5 | TEST_CASE("primitive typedefs", "[typedefs]") { REQUIRE(run("scripts/typedefs.as") == "3.141\n"); } 6 | --------------------------------------------------------------------------------