; ; Raw Game Engine ; Copyright (C) 2023 Ernest Deak ; ; This program is free software: you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, either version 3 of the License, or ; (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program. If not, see . ; %ifndef MAIN_MACROS %define MAIN_MACROS %ifidn __?OUTPUT_FORMAT?__, win64 %define WIN64 %elifidn __?OUTPUT_FORMAT?__, elf64 %define LINUX %endif %ifdef WIN64 %define ro_section .rdata %define code_section .code ; argument register order %define areg1 rcx %define areg2 rdx %define areg3 r8 %define areg4 r9 %define areg5 %fatal "Win64 only has 4 argument registers. The rest are passed on the stack." %define areg6 areg5 ; argument register order, 32bit %define areg1d ecx %define areg2d edx %define areg3d r8d %define areg4d r9d %define areg5d %fatal "Win64 only has 4 argument registers. The rest are passed on the stack." %define areg6d areg5d ;%define frame_reg r13 %define frame_reg rbp %else %define ro_section .rodata %define code_section .text ; argument register order %define areg1 rdi %define areg2 rsi %define areg3 rdx %define areg4 rcx %define areg5 r8 %define areg6 r9 ; argument register order 32bit %define areg1d edi %define areg2d esi %define areg3d edx %define areg4d ecx %define areg5d r8d %define areg6d r9d %define frame_reg rbp %endif %define __MEM_CALC_CALLED__ 0 ;; Macro to help calculate biggest memory chunk to use from ;; a list of struct names supplied to it %macro calc_mem_chunk 1-* %assign %%biggest 0 %rep %0 %if %%biggest < %[%1 %+ _size] %assign %%biggest %[%1 %+ _size] %xdefine LargestStruct %[%1] %endif %rotate 1 %endrep %assign %%aligned (((%%biggest//16)+1)*16) %xdefine RG_OBJECT_SIZE %%aligned %define LargestStruct_size RG_OBJECT_LIST_SIZE %define __MEM_CALC_CALLED__ 1 %endmacro %macro set_num_slots 1 %define RG_OBJECT_LIST_SIZE ((%1//64)+1)*64 %endmacro ; Macro that defines a macro that defines structure shortcuts %macro defstruct_shortcuts 1 %define %1(base,field) base + %1. %+ field %endmacro %macro localdef 2 %define %1 %2 %endmacro ; Load string to register ; TODO: add optional address name parameter ; NOTE: adding a way of using an already defined string ; if it already exists would be nice to have. since now it ; assembles all strings even duplicates %macro string2reg 1-2+ section ro_section %%str: db %2,0 section code_section lea %1, %%str %endmacro ;FIXME: gotta use lea or [label] ; a plain mov reg,addr will cause nasm to place ; an absolute value into the register ; which causes a DT_TEXTREL section to be generated in PIE code ; this isnt good as it requires runtime fixups of the .text section ; which is marked as READ_ONLY ; So our macros need to be aware of this %macro _typed_regmov 2 %ifstr %2 string2reg %1, %2 %elifnum %2 mov %1, %2 %elifid %2 ; this seems to be usually a register ; but can also be a memory address mov %1, %2 %else mov %1, %2 %endif %endmacro ; Macro call inst with respect to PLT and with proper argument passing order ; for linux %macro lxcall 1-7 %define %%func %1 %if(%0 > 1) _typed_regmov areg1, %2 %endif %if(%0 > 2) _typed_regmov areg2, %3 %endif %if(%0 > 3) _typed_regmov areg3, %4 %endif %if(%0 > 4) _typed_regmov areg4, %5 %endif %if(%0 > 5) _typed_regmov areg5, %6 %endif %if(%0 > 6) _typed_regmov areg6, %7 %endif %ifdef _%[%%func]_extern call %%func wrt ..plt %else call %%func %endif %endmacro ; Windows function call %macro wincall 1-7 %define %%func %1 %if(%0 > 1) _typed_regmov areg1, %2 %endif %if(%0 > 2) _typed_regmov areg2, %3 %endif %if(%0 > 3) _typed_regmov areg3, %4 %endif %if(%0 > 4) _typed_regmov areg4, %5 %endif %assign %%count 0 %xdefine %%c 0 %if(%0 > 5) %rep %0-5 ; intermediate scratch register to allow for passing ; [Address] notation into the function mov r11, %6 ; FIXME: doesnt work with our conditionals because we define ; another context for them and hence this and other macros fail ; we could make the home_reg_fixed and other macros ; global since they dont actually change (i think) mov qword [rsp+%%c+%$home_reg_fixed], r11 %rotate 1 %assign %%count %%count+1 %xdefine %%c %%c+8 %endrep %endif %if(%%count*8 > %$paramsize) %fatal "There are more function parameters for called functions then declared at function start: %%count > %$paramsize/8" %endif %ifdef _%[%%func]_extern call %%func wrt ..imagebase %else call %%func %endif %endmacro %macro bsdcall 1-7+ %fatal "BSD call not yet implemented" %endmacro ; External macro call with auto extern keyword ; and multi-os handling (Linux,Windows, still missing BSDs) %macro exmcall 1-2+ %ifndef _%1_extern extern %1 %define _%1_extern 1 %endif %if(%0 > 1) %ifdef WIN64 wincall %1,%2 %else lxcall %1,%2 %endif %else %ifdef WIN64 wincall %1 %else lxcall %1 %endif %endif %endmacro %macro mcall 1-2+ %if(%0 > 1) %ifdef WIN64 wincall %1,%2 %else lxcall %1,%2 %endif %else %ifdef WIN64 wincall %1 %else lxcall %1 %endif %endif %endmacro %macro variables 2-* %ifnctx function %fatal Variables can only be defined within a function context %endif %define %%unitsize %1 %rep %0-1 %rotate 1 ;%xdefine %%_tmp %[$ %+ %1] %xdefine %[%1] frame_reg-%%unitsize-%$localsize ;%xdefine %%_tmp frame_reg-%%unitsize-%$localsize %assign %$localsize %$localsize+%%unitsize %endrep %endmacro ; Simple alias to the above %defalias var variables %macro saveregs 1-* %assign %$rcount 0 %rep %0 %xdefine %$save_%[%$rcount] %1 %assign %$rcount %$rcount+1 %if %substr(%1,1,3) == "xmm" %assign %$localsize %$localsize+16 %xdefine %$saved_%1 frame_reg-%$localsize %else %assign %$localsize %$localsize+8 %xdefine %$saved_%1 frame_reg-%$localsize %endif %rotate 1 %endrep %endmacro ; NOTE: Using variables to store registers seems to be the best idea ; it will be less obvious in the disassembly, but in effect, we'd ; have to shrink rsp anyway (pushes do that) to account for it ; rbp+N can be used for parameters passed to us and to other functions (?) ; rsp+N can be used to pass parameters to other functions (?) ; rbp-N is used to store local variables and right now even registers ; that need to be saved. This is done automatically via the saveregs macro inside ; a function ; Function prologue made easier %macro funcstart 0-* 0 %push function endbr64 %assign %$rcount 0 %define %$USE_LEA 0 %stacksize flat64 %assign %$aligned_sub 0 %assign %$localsize 0 %ifdef WIN64 winfuncstart %1 %else push frame_reg mov frame_reg, rsp %endif %endmacro %macro def_stack_params 1 %assign %$numarg 1 %ifdef WIN64 %assign %$n 0x28 %else %assign %$n 0 %endif %rep %1 %xdefine stkparam%[%$numarg] rsp+%$n %assign %$n %$n+8 %assign %$numarg %$numarg+1 %endrep %endmacro %macro stksetup 0 ; we align the stack here to be a multiple of ; 16 due to hardware requirements ; also a lot of xmm instructions require ; the stack to be 16 byte aligned ; NASM doesn't do floating point arithmetic, so the bellow ; division evaluates to an unsigned integer without the decimal part %assign %$aligned_sub (%$localsize // 16) * 16 %if %$localsize > 0 %assign %$aligned_sub %$aligned_sub+16 %endif %if %$localsize > 0 sub rsp, %$aligned_sub %endif %if %$rcount > 0 %assign %%rn 0 %rep %$rcount %xdefine %%nextreg %$save_%[%%rn] %if %substr(%%nextreg,1,3) == "xmm" movdqa [%$saved_%%nextreg], %%nextreg %else mov [%$saved_%%nextreg], %%nextreg %endif %assign %%rn %%rn+1 ; %assign %$localsize %$localsize+8 %endrep %endif %endmacro %macro stkcleanup 0 restoreregs %if %$localsize > 0 add rsp, %$aligned_sub %endif %endmacro %macro restoreregs 0 %if %$rcount > 0 %assign %%rn %$rcount-1 %rep %$rcount %xdefine %%nextreg %$save_%[%%rn] %if %substr(%%nextreg,1,3) == "xmm" movdqa %%nextreg, [%$saved_%%nextreg] %else mov %%nextreg, [%$saved_%%nextreg] %endif %assign %%rn %%rn-1 %endrep %endif %endmacro ; Required parameter. It is impossible to infer the number of ; required extra stack parameters, so it must be specified at function ; definition time. It should be a number that represents the highest ; number of arguments after argument 4 to any function call within ; this function %macro winfuncstart 1 %define %$rcx_home rsp+0x08 %define %$rdx_home rsp+0x10 %define %$r8_home rsp+0x18 %define %$r9_home rsp+0x20 %define %$paramsize %1*8 %assign %$home_reg_fixed 0x20 %assign %$localsize %$home_reg_fixed + %$paramsize push frame_reg mov frame_reg, rsp %if(%$localsize > 4096) mov rax, %$localsize %ifndef __chkstk extern __chkstk %endif call __chkstk sub rsp, rax %endif %endmacro %macro epilogue 0 %ifnctx function %fatal "Epilogue used when not inside a functon" %endif stkcleanup pop frame_reg ret %endmacro ; Function epilogue made easier %macro funcend 0 epilogue %pop align 8 %endmacro %macro return 0-1 %if %0 == 1 mov rax, %1 %endif stkcleanup pop frame_reg ret %endmacro %macro ccall 2 j%-1 %%over call %2 %%over: %endmacro ;; Enum macro ;; @param 1 - number to start at ;; @param rest - rest of the arguments are constants %macro enum 2-* %assign %%n %1 %rep %0-1 %2 equ %%n %rotate 1 %assign %%n %%n+1 %endrep %endmacro ; conditionals ; FIXME: Doesnt seem to work well with windows binaries due to context-local ; variables/macros %macro ifc 1 %push ifctx j%-1 %$else %$if_%1: %endmacro %macro else 0 %ifnctx ifctx %error Expected 'if' before 'else' %endif jmp %$endif %$else: %push elsectx %endmacro %macro endif 0 %ifctx ifctx %$else: %pop %elifctx elsectx %pop %$endif: %pop %endif %endmacro ; Vectorized mov instruction macros for GP registers %macro vals2toreg 3 mov %5, %1 | (%2 << 32) %endmacro %macro vals4toreg 5 mov %5, %1 | (%2 << 16) | (%3 << 32) | (%4 << 48) %endmacro %macro vals8toreg 9 mov %9, %1 | (%2 << 8) | (%3 << 16) | (%4 << 24) | (%5 << 32) | (%6 << 40) | (%7 << 48) | (%8 << 56) %endmacro ; Other helpfull macros %macro mprintf 2 string2reg areg1, %1 %ifstr %2 string2reg areg2, %2 %else mov areg2, %2 %endif exmcall printf %endmacro %macro dbgprint 1-2+ %ifdef __?DEBUG_FORMAT?__ push areg1 push areg2 mprintf {"[DBG] ",%1,10}, {%2} pop areg2 pop areg1 %endif %endmacro %define uint128 tword %define uint64 qword %define uint32 dword %define uint16 word %define uint8 byte %defalias u8 uint8 %defalias u16 uint16 %defalias u32 uint32 %defalias u64 uint64 %defalias u128 uint128 %defalias Uint8 uint8 %defalias Uint16 uint16 %defalias Uint32 uint32 %defalias Uint64 uint64 %endif .