├── .gitignore ├── README.md ├── build.bat ├── core ├── allocators.sif ├── basic.sif ├── directx │ ├── d3d11.sif │ ├── d3dcommon.sif │ ├── dx.sif │ ├── dxgi.sif │ ├── dxgicommon.sif │ └── dxgitype.sif ├── dynamic_array.sif ├── hashtable.sif ├── json.sif ├── math.sif ├── os.sif ├── os_windows.sif ├── random.sif ├── runtime.sif ├── sdl2.sif ├── string_builder.sif ├── strings.sif ├── time.sif └── windows.sif ├── examples ├── demo │ └── main.sif ├── directx_win32_triangle │ ├── basic_pixel.hlsl │ ├── basic_vertex.hlsl │ ├── main.sif │ └── types.hlsl ├── entities │ └── main.sif ├── minidemo │ └── main.sif └── tests │ ├── defer_test.sif │ ├── dynamic_array_test.sif │ ├── file_io_test.sif │ ├── json_test.sif │ ├── local_declarations.sif │ ├── main.sif │ ├── miscellaneous_tests.sif │ ├── numbers_test.sif │ ├── strings_test.sif │ └── test_file.txt └── src ├── basic.cpp ├── basic.h ├── c_backend.cpp ├── c_backend.h ├── checker.cpp ├── checker.h ├── common.cpp ├── common.h ├── ir.cpp ├── ir.h ├── lexer.cpp ├── lexer.h ├── main.cpp ├── microsoft_craziness.h ├── os_windows.cpp ├── os_windows.h ├── parser.cpp ├── parser.h └── spinlock.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.pdb 3 | *.ll 4 | *.ilk 5 | *.obj 6 | *.bc 7 | *.dmp 8 | output.c 9 | 10 | .build/ 11 | .vs/ 12 | test.sif -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | del sif.exe >nul 2>nul 4 | 5 | set optimizations=/Od 6 | if "%1"=="release" ( 7 | set optimizations=/O2 8 | ) 9 | 10 | cl /nologo /MP /Zi /Fd %optimizations% /Iexternal src/main.cpp src/lexer.cpp src/parser.cpp src/checker.cpp src/c_backend.cpp src/basic.cpp src/os_windows.cpp src/common.cpp src/ir.cpp user32.lib advapi32.lib ole32.lib oleaut32.lib /EHsc /link /INCREMENTAL:no /OUT:sif.exe /DEBUG 11 | del *.obj >nul 2>nul -------------------------------------------------------------------------------- /core/allocators.sif: -------------------------------------------------------------------------------- 1 | #include "core:basic.sif" 2 | #include "core:dynamic_array.sif" 3 | 4 | proc buffer_allocate(buffer: []byte, offset: ^int, size: int, alignment: int, panic_on_oom: bool) : rawptr { 5 | // Don't allow allocations of zero size. This would likely return a 6 | // pointer to a different allocation, causing many problems. 7 | if (size == 0) { 8 | return null; 9 | } 10 | 11 | // todo(josh): The `align_forward()` call and the `start + size` below 12 | // that could overflow if the `size` or `align` parameters are super huge 13 | start := align_forward(offset^, alignment); 14 | 15 | // Don't allow allocations that would extend past the end of the buffer. 16 | if ((start + size) > buffer.count) { 17 | if (panic_on_oom) { 18 | assert(false); 19 | } 20 | return null; 21 | } 22 | 23 | offset^ = start + size; 24 | ptr := &buffer[start]; 25 | zero_memory(slice_ptr(ptr, size)); 26 | return ptr; 27 | } 28 | 29 | 30 | 31 | struct Arena_Allocator { 32 | memory: []byte; 33 | cur_offset: int; 34 | panic_on_oom: bool; 35 | } 36 | 37 | proc init_arena(arena: ^Arena_Allocator, backing: []byte, panic_on_oom: bool) { 38 | arena.memory = backing; 39 | arena.cur_offset = 0; 40 | arena.panic_on_oom = panic_on_oom; 41 | } 42 | 43 | proc arena_alloc(allocator: rawptr, size: int, align: int) : rawptr { 44 | arena := cast(^Arena_Allocator, allocator); 45 | return buffer_allocate(arena.memory, &arena.cur_offset, size, align, arena.panic_on_oom); 46 | } 47 | 48 | proc arena_free(allocator: rawptr, ptr: rawptr) { 49 | // note(josh): freeing from arenas does nothing. 50 | } 51 | 52 | proc arena_reset(arena: ^Arena_Allocator) { 53 | arena.cur_offset = 0; 54 | } 55 | 56 | proc arena_to_allocator(arena: ^Arena_Allocator) : Allocator { 57 | a: Allocator; 58 | a.data = arena; 59 | a.alloc_proc = arena_alloc; 60 | a.free_proc = arena_free; 61 | return a; 62 | } 63 | 64 | 65 | 66 | struct Dynamic_Arena { 67 | arenas: Dynamic_Array!(Arena_Allocator); 68 | chunk_size: int; 69 | growth_rate: float; 70 | current_arena_index: int; 71 | backing_allocator: Allocator; 72 | leaked_allocations: Dynamic_Array!(rawptr); // if an allocation is bigger than the chunk size, we fall back to the backing allocator and save it here to be freed in destroy_dynamic_arena() 73 | } 74 | 75 | proc init_dynamic_arena(dynamic_arena: ^Dynamic_Arena, chunk_size: int, backing_allocator: Allocator) { 76 | dynamic_arena.backing_allocator = backing_allocator; 77 | dynamic_arena.arenas.allocator = dynamic_arena.backing_allocator; 78 | dynamic_arena.leaked_allocations.allocator = dynamic_arena.backing_allocator; 79 | dynamic_arena.chunk_size = chunk_size; 80 | dynamic_arena.growth_rate = 2; 81 | 82 | arena: Arena_Allocator; 83 | init_arena(&arena, new_slice(byte, chunk_size, dynamic_arena.backing_allocator), false); 84 | append(&dynamic_arena.arenas, arena); 85 | } 86 | 87 | proc dynamic_arena_alloc(allocator: rawptr, size: int, align: int) : rawptr { 88 | dyn := cast(^Dynamic_Arena, allocator); 89 | assert(dyn != null); 90 | current_arena := &dyn.arenas[dyn.current_arena_index]; 91 | ptr := arena_alloc(current_arena, size, align); 92 | 93 | if (ptr == null) { 94 | // we've reached the end of the arena 95 | if (dyn.current_arena_index+1 >= dyn.arenas.count) { 96 | // make a new arena 97 | current_arena = append_empty(&dyn.arenas); 98 | dyn.chunk_size = cast(int, cast(float, dyn.chunk_size) * dyn.growth_rate); 99 | init_arena(current_arena, new_slice(byte, dyn.chunk_size, dyn.backing_allocator), false); 100 | } 101 | else { 102 | // use the next one in the list 103 | current_arena = &dyn.arenas[dyn.current_arena_index+1]; 104 | } 105 | dyn.current_arena_index += 1; 106 | 107 | // retry the allocation 108 | ptr = arena_alloc(current_arena, size, align); 109 | if (ptr == null) { 110 | // if it STILL failed then that means that the allocation is too big for our chunk size. fallback to the backing allocator 111 | ptr = alloc(size, align, dyn.backing_allocator); 112 | assert(ptr != null); 113 | append(&dyn.leaked_allocations, ptr); 114 | } 115 | } 116 | assert(ptr != null); 117 | return ptr; 118 | } 119 | 120 | proc dynamic_arena_free(allocator: rawptr, ptr: rawptr) { 121 | // note(josh): freeing from arenas does nothing. 122 | } 123 | 124 | proc dynamic_arena_reset(dynamic_arena: ^Dynamic_Arena) { 125 | for (idx := 0; idx < dynamic_arena.arenas.count; idx += 1) { 126 | arena_reset(&dynamic_arena.arenas[idx]); 127 | } 128 | dynamic_arena.current_arena_index = 0; 129 | } 130 | 131 | proc dynamic_arena_to_allocator(dynamic_arena: ^Dynamic_Arena) : Allocator { 132 | a: Allocator; 133 | a.data = dynamic_arena; 134 | a.alloc_proc = dynamic_arena_alloc; 135 | a.free_proc = dynamic_arena_free; 136 | return a; 137 | } 138 | 139 | proc destroy_dynamic_arena(dynamic_arena: Dynamic_Arena) { 140 | for (idx := 0; idx < dynamic_arena.arenas.count; idx += 1) { 141 | delete_slice(dynamic_arena.arenas[idx].memory, dynamic_arena.backing_allocator); 142 | } 143 | destroy_dynamic_array(dynamic_arena.arenas); 144 | for (idx := 0; idx < dynamic_arena.leaked_allocations.count; idx += 1) { 145 | free(dynamic_arena.leaked_allocations[idx], dynamic_arena.backing_allocator); 146 | } 147 | destroy_dynamic_array(dynamic_arena.leaked_allocations); 148 | } -------------------------------------------------------------------------------- /core/directx/dx.sif: -------------------------------------------------------------------------------- 1 | #foreign_system_import "d3d11.lib"; 2 | #foreign_system_import "d3dcompiler.lib"; 3 | 4 | proc D3D11CreateDevice( 5 | pAdapter : ^IDXGIAdapter, 6 | DriverType : UINT, 7 | Software : HMODULE, 8 | Flags : UINT, 9 | pFeatureLevels : ^D3D_FEATURE_LEVEL, 10 | FeatureLevels : u32, 11 | SDKVersion : u32, 12 | ppDevice : ID3D11Device, 13 | pFeatureLevel : ^D3D_FEATURE_LEVEL, 14 | ppImmediateContext : ID3D11DeviceContext) : HRESULT #foreign "d3d11"; 15 | 16 | proc D3D11CreateDeviceAndSwapChain( 17 | pAdapter : ^IDXGIAdapter, 18 | DriverType : UINT, 19 | Software : HMODULE, 20 | Flags : UINT, 21 | pFeatureLevels : ^D3D_FEATURE_LEVEL, 22 | FeatureLevels : u32, 23 | SDKVersion : u32, 24 | pSwapChainDesc : ^DXGI_SWAP_CHAIN_DESC, 25 | ppSwapChain : ^^IDXGISwapChain, 26 | ppDevice : ^^ID3D11Device, 27 | pFeatureLevel : ^D3D_FEATURE_LEVEL, 28 | ppImmediateContext : ^^ID3D11DeviceContext) : HRESULT #foreign "d3d11"; 29 | 30 | proc D3DCompileFromFile( 31 | pFileName : LPCWSTR, 32 | pDefines : rawptr,//^D3D_SHADER_MACRO, 33 | pInclude : ^ID3DInclude, 34 | pEntrypoint : cstring, 35 | pTarget : cstring, 36 | Flags1 : UINT, 37 | Flags2 : UINT, 38 | ppCode : ^^ID3D10Blob, 39 | ppErrorMsgs : ^^ID3D10Blob) : HRESULT #foreign "d3dcompiler"; 40 | 41 | proc D3DCompile( 42 | pSrcData : cstring, 43 | SrcDataSize : uint, 44 | pSourceName : cstring, 45 | pDefines : rawptr, 46 | pInclude : ^ID3DInclude, 47 | pEntrypoint : cstring, 48 | pTarget : cstring, 49 | Flags1 : UINT, 50 | Flags2 : UINT, 51 | ppCode : ^^ID3D10Blob, 52 | ppErrorMsgs : ^^ID3D10Blob) : HRESULT #foreign "d3dcompiler"; 53 | 54 | const D3D11_SDK_VERSION := 7; 55 | 56 | const UINT8 := u8; 57 | const UINT16 := u16; 58 | const UINT64 := u64; 59 | const USHORT := u16; 60 | const ULONG := u32; 61 | const ULONGLONG := u64; 62 | const LUID := _LUID; 63 | const D3D11_RECT := RECT; 64 | const LPVOID := rawptr; 65 | const D3D_FEATURE_LEVEL := UINT; 66 | const D3DCOLORVALUE := _D3DCOLORVALUE; 67 | const DXGI_RGBA := D3DCOLORVALUE; 68 | 69 | const DXGI_FORMAT := UINT; 70 | const D3D11_PRIMITIVE_TOPOLOGY := UINT; 71 | const D3D11_DEVICE_CONTEXT_TYPE := UINT; 72 | const D3D11_BLEND := UINT; 73 | const D3D11_BLEND_OP := UINT; 74 | const D3D11_FILL_MODE := UINT; 75 | const D3D11_VIDEO_DECODER_BUFFER_TYPE := UINT; 76 | const D3D11_CULL_MODE := UINT; 77 | const D3D11_COUNTER_TYPE := UINT; 78 | const D3D11_USAGE := UINT; 79 | const D3D11_RESOURCE_DIMENSION := UINT; 80 | const D3D11_SRV_DIMENSION := UINT; 81 | const D3D11_UAV_DIMENSION := UINT; 82 | const D3D11_RTV_DIMENSION := UINT; 83 | const D3D11_QUERY := UINT; 84 | const D3D11_MAP := UINT; 85 | const D3D11_FEATURE := UINT; 86 | const D3D11_FENCE_FLAG := UINT; 87 | const D3D11_VIDEO_PROCESSOR_ALPHA_FILL_MODE := UINT; 88 | const DXGI_COLOR_SPACE_TYPE := UINT; 89 | const D3D11_VIDEO_FRAME_FORMAT := UINT; 90 | const D3D11_VIDEO_PROCESSOR_OUTPUT_RATE := UINT; 91 | const D3D11_VIDEO_PROCESSOR_STEREO_FORMAT := UINT; 92 | const D3D11_VIDEO_PROCESSOR_STEREO_FLIP_MODE := UINT; 93 | const D3D11_VIDEO_PROCESSOR_FILTER := UINT; 94 | const D3D11_VIDEO_PROCESSOR_ROTATION := UINT; 95 | const D3D11_CRYPTO_SESSION_STATUS := UINT; 96 | const DXGI_HDR_METADATA_TYPE := UINT; 97 | const D3D11_AUTHENTICATED_CHANNEL_TYPE := UINT; 98 | const DXGI_MODE_ROTATION := UINT; 99 | const DXGI_SWAP_EFFECT := UINT; 100 | const DXGI_MODE_SCANLINE_ORDER := UINT; 101 | const DXGI_MODE_SCALING := UINT; 102 | const DXGI_RESIDENCY := UINT; 103 | const DXGI_OFFER_RESOURCE_PRIORITY := UINT; 104 | const DXGI_GRAPHICS_PREEMPTION_GRANULARITY := UINT; 105 | const DXGI_COMPUTE_PREEMPTION_GRANULARITY := UINT; 106 | const DXGI_MEMORY_SEGMENT_GROUP := UINT; 107 | const DXGI_MULTIPLANE_OVERLAY_YCbCr_FLAGS := UINT; 108 | const DXGI_DEBUG_ID := UINT; 109 | const DXGI_RECLAIM_RESOURCE_RESULTS := UINT; 110 | 111 | const PFN_DESTRUCTION_CALLBACK := proc(pData: rawptr); 112 | const DXGI_INFO_QUEUE_MESSAGE_ID := i32; 113 | const D3D11_AUTHENTICATED_PROTECTION_FLAGS := u32; 114 | const APP_DEPRECATED_HRESULT := HRESULT; 115 | 116 | const S_FALSE := 1; 117 | 118 | const D3D11_ERROR_FILE_NOT_FOUND := 0x887C002; 119 | const D3D11_ERROR_TOO_MANY_UNIQUE_STATE_OBJECTS := 0x887C0001; 120 | const D3D11_ERROR_TOO_MANY_UNIQUE_VIEW_OBJECTS := 0x887C0003; 121 | const D3D11_ERROR_DEFERRED_CONTEXT_MAP_WITHOUT_INITIAL_DISCARD := 0x887C0004; 122 | const D3DERR_INVALIDCALL := 0x887A0001; 123 | const D3DERR_WASSTILLDRAWING := 0x887A000A; 124 | 125 | const DXGI_ERROR_ACCESS_DENIED := 0x887A002B; 126 | const DXGI_ERROR_ACCESS_LOST := 0x887A0026; 127 | const DXGI_ERROR_ALREADY_EXISTS := 0x887A0036; 128 | const DXGI_ERROR_CANNOT_PROTECT_CONTENT := 0x887A002A; 129 | const DXGI_ERROR_DEVICE_HUNG := 0x887A0006; 130 | const DXGI_ERROR_DEVICE_REMOVED := 0x887A0005; 131 | const DXGI_ERROR_DEVICE_RESET := 0x887A0007; 132 | const DXGI_ERROR_DRIVER_INTERNAL_ERROR := 0x887A0020; 133 | const DXGI_ERROR_FRAME_STATISTICS_DISJOINT := 0x887A000B; 134 | const DXGI_ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE := 0x887A000C; 135 | const DXGI_ERROR_INVALID_CALL := 0x887A0001; 136 | const DXGI_ERROR_MORE_DATA := 0x887A0003; 137 | const DXGI_ERROR_NAME_ALREADY_EXISTS := 0x887A002C; 138 | const DXGI_ERROR_NONEXCLUSIVE := 0x887A0021; 139 | const DXGI_ERROR_NOT_CURRENTLY_AVAILABLE := 0x887A0022; 140 | const DXGI_ERROR_NOT_FOUND := 0x887A0002; 141 | const DXGI_ERROR_REMOTE_CLIENT_DISCONNECTED := 0x887A0023; 142 | const DXGI_ERROR_REMOTE_OUTOFMEMORY := 0x887A0024; 143 | const DXGI_ERROR_RESTRICT_TO_OUTPUT_STALE := 0x887A0029; 144 | const DXGI_ERROR_SDK_COMPONENT_MISSING := 0x887A002D; 145 | const DXGI_ERROR_SESSION_DISCONNECTED := 0x887A0028; 146 | const DXGI_ERROR_UNSUPPORTED := 0x887A0004; 147 | const DXGI_ERROR_WAIT_TIMEOUT := 0x887A0027; 148 | const DXGI_ERROR_WAS_STILL_DRAWING := 0x887A000A; 149 | 150 | struct ID3DInclude { 151 | close: proc(data: rawptr) : HRESULT; 152 | open: proc(include_type: D3DIncludeType, file_name: ^byte, parent_data: rawptr, out_data: ^rawptr, out_bytes: ^u32) : HRESULT; 153 | } 154 | 155 | enum D3DIncludeType i32 { 156 | Local; 157 | System; 158 | D3D10_Local; 159 | D3D10_System; 160 | Force_Dword; 161 | } 162 | 163 | const D3DCOMPILE_DEBUG := 1 << 0; 164 | const D3DCOMPILE_SKIP_VALIDATION := 1 << 1; 165 | const D3DCOMPILE_SKIP_OPTIMIZATION := 1 << 2; 166 | const D3DCOMPILE_PACK_MATRIX_ROW_MAJOR := 1 << 3; 167 | const D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR := 1 << 4; 168 | const D3DCOMPILE_PARTIAL_PRECISION := 1 << 5; 169 | const D3DCOMPILE_FORCE_VS_SOFTWARE_NO_OPT := 1 << 6; 170 | const D3DCOMPILE_FORCE_PS_SOFTWARE_NO_OPT := 1 << 7; 171 | const D3DCOMPILE_NO_PRESHADER := 1 << 8; 172 | const D3DCOMPILE_AVOID_FLOW_CONTROL := 1 << 9; 173 | const D3DCOMPILE_PREFER_FLOW_CONTROL := 1 << 10; 174 | const D3DCOMPILE_ENABLE_STRICTNESS := 1 << 11; 175 | const D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY := 1 << 12; 176 | const D3DCOMPILE_IEEE_STRICTNESS := 1 << 13; 177 | const D3DCOMPILE_OPTIMIZATION_LEVEL0 := 1 << 14; 178 | const D3DCOMPILE_OPTIMIZATION_LEVEL1 := 0; 179 | const D3DCOMPILE_OPTIMIZATION_LEVEL2 := (1 << 14) | (1 << 15); 180 | const D3DCOMPILE_OPTIMIZATION_LEVEL3 := 1 << 15; 181 | const D3DCOMPILE_WARNINGS_ARE_ERRORS := 1 << 18; 182 | const D3DCOMPILE_RESOURCES_MAY_ALIAS := 1 << 19; 183 | const D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES := 1 << 20; 184 | const D3DCOMPILE_ALL_RESOURCES_BOUND := 1 << 21; 185 | 186 | const DXGI_USAGE_SHADER_INPUT := 0x00000010; 187 | const DXGI_USAGE_RENDER_TARGET_OUTPUT := 0x00000020; 188 | const DXGI_USAGE_BACK_BUFFER := 0x00000040; 189 | const DXGI_USAGE_SHARED := 0x00000080; 190 | const DXGI_USAGE_READ_ONLY := 0x00000100; 191 | const DXGI_USAGE_DISCARD_ON_PRESENT := 0x00000200; 192 | const DXGI_USAGE_UNORDERED_ACCESS := 0x00000400; 193 | 194 | // struct IUnknown { 195 | // vtbl: ^IUnknown_vtbl(IUnknown); 196 | // } 197 | 198 | // struct IUnknown_vtbl!($T: typeid) { 199 | // query_interface: proc(^T, ^rawptr) : HRESULT; 200 | // add_ref: proc(^T) : u32; 201 | // release: proc(^T) : u32; 202 | // } 203 | 204 | struct SIZE { 205 | cx: u32; 206 | cy: u32; 207 | } -------------------------------------------------------------------------------- /core/directx/dxgicommon.sif: -------------------------------------------------------------------------------- 1 | struct DXGI_RATIONAL { 2 | Numerator: UINT; 3 | Denominator: UINT; 4 | } 5 | struct DXGI_SAMPLE_DESC { 6 | Count: UINT; 7 | Quality: UINT; 8 | } 9 | //DXGI_COLOR_SPACE_TYPE 10 | const DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709 := 0; 11 | const DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 := 1; 12 | const DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P709 := 2; 13 | const DXGI_COLOR_SPACE_RGB_STUDIO_G22_NONE_P2020 := 3; 14 | const DXGI_COLOR_SPACE_RESERVED := 4; 15 | const DXGI_COLOR_SPACE_YCBCR_FULL_G22_NONE_P709_X601 := 5; 16 | const DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601 := 6; 17 | const DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601 := 7; 18 | const DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709 := 8; 19 | const DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709 := 9; 20 | const DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020 := 10; 21 | const DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020 := 11; 22 | const DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 := 12; 23 | const DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_LEFT_P2020 := 13; 24 | const DXGI_COLOR_SPACE_RGB_STUDIO_G2084_NONE_P2020 := 14; 25 | const DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_TOPLEFT_P2020 := 15; 26 | const DXGI_COLOR_SPACE_YCBCR_STUDIO_G2084_TOPLEFT_P2020 := 16; 27 | const DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020 := 17; 28 | const DXGI_COLOR_SPACE_YCBCR_STUDIO_GHLG_TOPLEFT_P2020 := 18; 29 | const DXGI_COLOR_SPACE_YCBCR_FULL_GHLG_TOPLEFT_P2020 := 19; 30 | const DXGI_COLOR_SPACE_CUSTOM := 0xFFFFFFFF; 31 | -------------------------------------------------------------------------------- /core/directx/dxgitype.sif: -------------------------------------------------------------------------------- 1 | struct DXGI_RGB { 2 | Red: float; 3 | Green: float; 4 | Blue: float; 5 | } 6 | struct _D3DCOLORVALUE { 7 | r: float; 8 | g: float; 9 | b: float; 10 | a: float; 11 | } 12 | struct DXGI_GAMMA_CONTROL { 13 | Scale: DXGI_RGB; 14 | Offset: DXGI_RGB; 15 | GammaCurve: [1025]DXGI_RGB; 16 | } 17 | struct DXGI_GAMMA_CONTROL_CAPABILITIES { 18 | ScaleAndOffsetSupported: BOOL; 19 | MaxConvertedValue: float; 20 | MinConvertedValue: float; 21 | NumGammaControlPoints: UINT; 22 | ControlPointPositions: [1025]float; 23 | } 24 | struct DXGI_MODE_DESC { 25 | Width: UINT; 26 | Height: UINT; 27 | RefreshRate: DXGI_RATIONAL; 28 | Format: DXGI_FORMAT; 29 | ScanlineOrdering: UINT; 30 | Scaling: UINT; 31 | } 32 | struct DXGI_JPEG_DC_HUFFMAN_TABLE { 33 | CodeCounts: [12]BYTE; 34 | CodeValues: [12]BYTE; 35 | } 36 | struct DXGI_JPEG_AC_HUFFMAN_TABLE { 37 | CodeCounts: [16]BYTE; 38 | CodeValues: [162]BYTE; 39 | } 40 | struct DXGI_JPEG_QUANTIZATION_TABLE { 41 | Elements: [64]BYTE; 42 | } 43 | //DXGI_MODE_SCANLINE_ORDER 44 | const DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED := 0; 45 | const DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE := 1; 46 | const DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST := 2; 47 | const DXGI_MODE_SCANLINE_ORDER_LOWER_FIELD_FIRST := 3; 48 | //DXGI_MODE_SCALING 49 | const DXGI_MODE_SCALING_UNSPECIFIED := 0; 50 | const DXGI_MODE_SCALING_CENTERED := 1; 51 | const DXGI_MODE_SCALING_STRETCHED := 2; 52 | //DXGI_MODE_ROTATION 53 | const DXGI_MODE_ROTATION_UNSPECIFIED := 0; 54 | const DXGI_MODE_ROTATION_IDENTITY := 1; 55 | const DXGI_MODE_ROTATION_ROTATE90 := 2; 56 | const DXGI_MODE_ROTATION_ROTATE180 := 3; 57 | const DXGI_MODE_ROTATION_ROTATE270 := 4; 58 | -------------------------------------------------------------------------------- /core/dynamic_array.sif: -------------------------------------------------------------------------------- 1 | #include "core:basic.sif" 2 | 3 | struct Dynamic_Array!($T: typeid) { 4 | elements: []T; 5 | count: int; 6 | capacity: int; 7 | allocator: Allocator; 8 | operator [](dyn: >Dynamic_Array!(T), index: int) : >T { 9 | return dyn.elements[index]; 10 | } 11 | } 12 | 13 | proc make_dynamic_array($T: typeid, capacity: int, allocator: Allocator) : Dynamic_Array!(T) { 14 | dyn: Dynamic_Array!(T); 15 | dyn.allocator = allocator; 16 | reserve(&dyn, capacity); 17 | return dyn; 18 | } 19 | 20 | proc destroy_dynamic_array(dyn: Dynamic_Array!($T)) { 21 | if (dyn.elements.data != null) { 22 | free(dyn.elements.data, dyn.allocator); 23 | } 24 | } 25 | 26 | proc append(dyn: ^Dynamic_Array!($T), value: T) : ^T { 27 | _dynamic_array_check_allocator(dyn); 28 | resize_if_at_capacity(dyn); 29 | assert(dyn.elements.count < dyn.capacity); 30 | assert(dyn.elements.count == dyn.count); 31 | dyn.elements.count += 1; 32 | dyn.elements[dyn.count] = value; 33 | ptr := &dyn.elements[dyn.count]; 34 | dyn.count += 1; 35 | return ptr; 36 | } 37 | 38 | proc append_empty(dyn: ^Dynamic_Array!($T)) : ^T { 39 | t: T; 40 | return append(dyn, t); 41 | } 42 | 43 | proc insert(dyn: ^Dynamic_Array!($T), index: int, value: T) : ^T { 44 | _dynamic_array_check_allocator(dyn); 45 | resize_if_at_capacity(dyn); 46 | if (index >= dyn.count) { 47 | // todo(josh): when we have #caller_location call sif_bounds_check() here 48 | print("insert() out of bounds: %\n", index); 49 | assert(false); 50 | } 51 | if (index < 0) { 52 | // todo(josh): when we have #caller_location call sif_bounds_check() here 53 | print("insert() out of bounds: %\n", index); 54 | assert(false); 55 | } 56 | dyn.count += 1; 57 | dyn.elements.count += 1; 58 | memmove(&dyn.elements[index+1], &dyn.elements[index], cast(u64, (dyn.count - index) * sizeof(T))); 59 | ptr := &dyn^[index]; 60 | ptr^ = value; 61 | return ptr; 62 | } 63 | 64 | proc reserve(dyn: ^Dynamic_Array!($T), new_cap: int) { 65 | old_data := dyn.elements.data; 66 | dyn.elements.data = cast(^T, alloc(new_cap * sizeof(T), DEFAULT_ALIGNMENT, dyn.allocator)); 67 | dyn.capacity = new_cap; 68 | if (old_data != null) { 69 | memcpy(dyn.elements.data, old_data, cast(u64, dyn.count * sizeof(T))); 70 | free(old_data, dyn.allocator); 71 | } 72 | } 73 | 74 | proc pop(dyn: ^Dynamic_Array!($T)) : T { 75 | if (dyn.count == 0) { 76 | print("pop() called with empty dynamic array\n"); 77 | assert(false); 78 | } 79 | value := dyn^[dyn.count-1]; 80 | dyn.elements.count -= 1; 81 | dyn.count -= 1; 82 | return value; 83 | } 84 | 85 | proc unordered_remove(dyn: ^Dynamic_Array!($T), index: int) : T { 86 | value := dyn^[index]; 87 | dyn^[index] = dyn^[dyn.count-1]; 88 | dyn.elements.count -= 1; 89 | dyn.count -= 1; 90 | return value; 91 | } 92 | 93 | proc ordered_remove(dyn: ^Dynamic_Array!($T), index: int) : T { 94 | value := dyn^[index]; 95 | if (index != dyn.count-1) { 96 | dst: rawptr = &dyn^[index]; 97 | src: rawptr = &dyn^[index+1]; 98 | memmove(dst, src, cast(u64, (dyn.count-index-1) * sizeof(T))); 99 | } 100 | dyn.elements.count -= 1; 101 | dyn.count -= 1; 102 | return value; 103 | } 104 | 105 | proc clear_dynamic_array(dyn: ^Dynamic_Array!($T)) { 106 | dyn.elements.count = 0; 107 | dyn.count = 0; 108 | } 109 | 110 | proc resize_if_at_capacity(dyn: ^Dynamic_Array!($T)) { 111 | if (dyn.count == dyn.capacity) { 112 | reserve(dyn, 8 + dyn.capacity * 2); 113 | } 114 | } 115 | 116 | proc _dynamic_array_check_allocator(dyn: ^Dynamic_Array!($T)) { 117 | if (dyn.allocator.alloc_proc == null) { 118 | print("Error: Allocator wasn't set on Dynamic_Array(%).\n", T); 119 | assert(false); 120 | } 121 | } -------------------------------------------------------------------------------- /core/hashtable.sif: -------------------------------------------------------------------------------- 1 | #include "core:basic.sif" 2 | #include "core:math.sif" 3 | 4 | struct Hashtable!($Key: typeid, $Value: typeid) { 5 | key_headers: []Key_Header!(Key); 6 | values: []Key_Value!(Key, Value); 7 | count: int; 8 | allocator: Allocator; 9 | } 10 | 11 | struct Key_Header!($Key: typeid) { 12 | filled: bool; 13 | key: Key; 14 | } 15 | 16 | struct Key_Value!($Key: typeid, $Value: typeid) { 17 | filled: bool; 18 | hash: u64; 19 | value: Value; 20 | } 21 | 22 | const INITIAL_HASHTABLE_SIZE := 31; 23 | 24 | proc make_hashtable($Key: typeid, $Value: typeid, allocator: Allocator) : Hashtable!(Key, Value) { 25 | hashtable: Hashtable!(Key, Value); 26 | hashtable.allocator = allocator; 27 | hashtable.key_headers = new_slice(Key_Header!(Key), INITIAL_HASHTABLE_SIZE, hashtable.allocator); 28 | hashtable.values = new_slice(Key_Value!(Key, Value), INITIAL_HASHTABLE_SIZE, hashtable.allocator); 29 | return hashtable; 30 | } 31 | 32 | proc hashtable_insert(table: ^Hashtable!($Key, $Value), key: Key, value: Value) { 33 | if (cast(f64, table.count) >= cast(f64, table.key_headers.count)*0.75) { 34 | old_key_headers := table.key_headers; 35 | old_values := table.values; 36 | old_length := old_key_headers.count; 37 | old_count := table.count; 38 | 39 | const INITIAL_SIZE := 31; 40 | new_len := next_power_of_2(INITIAL_SIZE + old_length); 41 | table.key_headers = new_slice(Key_Header!(Key), new_len, table.allocator); 42 | table.values = new_slice(Key_Value!(Key, Value), new_len, table.allocator); 43 | table.count = 0; 44 | 45 | for (header_idx := 0; header_idx < old_length; header_idx += 1) { 46 | old_key_header := &old_key_headers[header_idx]; 47 | if (old_key_header.filled) { 48 | hashtable_insert(table, old_key_header.key, old_values[header_idx].value); 49 | } 50 | } 51 | 52 | delete_slice(old_key_headers, table.allocator); 53 | delete_slice(old_values, table.allocator); 54 | } 55 | 56 | key_value_index: int = -1; 57 | h := _hashtable_hash_key(key); 58 | len_indices := cast(u64, table.key_headers.count); 59 | hash_idx := h % len_indices; 60 | for (idx := hash_idx; idx < len_indices; idx += 1) { 61 | value_ptr := &table.values[cast(int, idx)]; 62 | if (!value_ptr.filled) { 63 | key_value_index = cast(int, idx); 64 | break; 65 | } 66 | } 67 | if (key_value_index == -1) { 68 | for (idx : u64 = 0; idx < hash_idx; idx += 1) { 69 | value_ptr := &table.values[cast(int, idx)]; 70 | if (!value_ptr.filled) { 71 | key_value_index = cast(int, idx); 72 | break; 73 | } 74 | } 75 | } 76 | assert(key_value_index >= 0); 77 | 78 | table.values[key_value_index] = .{true, h, value}; 79 | table.key_headers[key_value_index] = .{true, key}; 80 | table.count += 1; 81 | } 82 | 83 | proc hashtable_get(table: ^Hashtable!($Key, $Value), key: Key) : ^Value { 84 | key_header: ^Key_Header!(Key); 85 | index: u64; 86 | _hashtable_get_key_header(table, key, &key_header, &index); 87 | if (key_header == null) { 88 | return null; 89 | } 90 | return &table.values[cast(int, index)].value; 91 | } 92 | 93 | proc _hashtable_get_key_header(table: ^Hashtable!($Key, $Value), key: Key, out_key_header: ^^Key_Header!(Key), out_index: ^u64) { 94 | out_key_header^ = null; 95 | out_index^ = 0; 96 | h := _hashtable_hash_key(key); 97 | len_key_headers := cast(u64, table.key_headers.count); 98 | hash_idx := h % len_key_headers; 99 | for (idx := hash_idx; idx < len_key_headers; idx += 1) { 100 | header := &table.key_headers[cast(int, idx)]; 101 | if (!header.filled) { 102 | return; 103 | } 104 | if (header.key == key) { 105 | out_key_header^ = header; 106 | out_index^ = idx; 107 | return; 108 | } 109 | } 110 | for (idx : u64 = 0; idx < hash_idx; idx += 1) { 111 | header := &table.key_headers[cast(int, idx)]; 112 | if (!header.filled) { 113 | return; 114 | } 115 | if (header.key == key) { 116 | out_key_header^ = header; 117 | out_index^ = idx; 118 | return; 119 | } 120 | } 121 | // should be unreachable 122 | assert(false); 123 | } 124 | 125 | proc _hashtable_hash_key(key: $Key) : u64 { 126 | // todo(josh): hash string data differently 127 | 128 | // note(josh): fnv64 129 | h : u64 = 0xcbf29ce484222325; 130 | key_slice := slice_ptr(cast(^byte, &key), sizeof(Key)); 131 | for (i := 0; i < sizeof(Key); i += 1) { 132 | h = (h * 0x100000001b3) ~ cast(u64, key_slice[i]); 133 | } 134 | return h; 135 | } 136 | 137 | proc hashtable_remove(table: ^Hashtable!($Key, $Value), key: Key) { 138 | key_header: ^Key_Header!(Key); 139 | key_value_idx: u64; 140 | _hashtable_get_key_header(table, key, &key_header, &key_value_idx); 141 | if (key_header == null) { 142 | return; 143 | } 144 | key_value := &table.values[cast(int, key_value_idx)]; 145 | len_values := cast(u64, table.values.count); 146 | hash_idx := key_value.hash % len_values; 147 | last_thing_that_hashed_to_the_same_idx: ^Key_Value!(Key, Value); 148 | last_thing_index: u64; 149 | done := false; 150 | for (idx := key_value_idx+1; idx < len_values; idx += 1) { 151 | value := &table.values[cast(int, idx)]; 152 | if (!value.filled) { 153 | done = true; 154 | break; 155 | } 156 | if (value.hash % len_values == hash_idx) { 157 | last_thing_that_hashed_to_the_same_idx = value; 158 | last_thing_index = idx; 159 | } 160 | } 161 | if (!done) { 162 | for (idx : u64 = 0; idx < hash_idx; idx += 1) { 163 | value := &table.values[cast(int, idx)]; 164 | if (!value.filled) { 165 | break; 166 | } 167 | if (value.hash % len_values == hash_idx) { 168 | last_thing_that_hashed_to_the_same_idx = value; 169 | last_thing_index = idx; 170 | } 171 | } 172 | } 173 | 174 | 175 | if (last_thing_that_hashed_to_the_same_idx != null) { 176 | key_header^ = table.key_headers[cast(int, last_thing_index)]; 177 | key_value^ = last_thing_that_hashed_to_the_same_idx^; 178 | table.key_headers[cast(int, last_thing_index)].filled = false; 179 | last_thing_that_hashed_to_the_same_idx.filled = false; 180 | } 181 | else { 182 | key_header.filled = false; 183 | key_value.filled = false; 184 | } 185 | 186 | table.count -= 1; 187 | } 188 | 189 | // #include "core:time.sif" 190 | 191 | // proc main() { 192 | // timer: Timer; 193 | // init_timer(&timer); 194 | 195 | // const NUM_ELEMS := 100 * 10000; 196 | 197 | // my_table := make_hashtable(int, int, default_allocator()); 198 | // { 199 | // insert_start := get_time(timer); 200 | // for (i := 0; i < NUM_ELEMS; i += 1) { 201 | // hashtable_insert(&my_table, i, i * 3); 202 | // } 203 | // insert_end := get_time(timer); 204 | // time := (insert_end-insert_start); 205 | // print("My map inserting % elements: %s\n", NUM_ELEMS, time); 206 | // } 207 | 208 | // { 209 | // lookup_start := get_time(timer); 210 | // for (i := 0; i < NUM_ELEMS; i += 1) { 211 | // val := hashtable_get(&my_table, i); 212 | // assert(val != null); 213 | // assert(val^ == i * 3); 214 | // } 215 | // lookup_end := get_time(timer); 216 | // time := (lookup_end-lookup_start); 217 | // print("My map retrieving % elements: %s\n", NUM_ELEMS, time); 218 | // } 219 | 220 | // { 221 | // // iterate_start := get_time(timer); 222 | // // for header, idx in my_table.key_headers { 223 | // // if !header.filled do continue; 224 | // // key := header.key; 225 | // // value := my_table.values[idx].value; 226 | // // assert(value == key * 3); 227 | // // } 228 | // // iterate_end := get_time(timer); 229 | // // print("My map iterating % elements: %s\n", NUM_ELEMS, (iterate_end-iterate_start)); 230 | // } 231 | 232 | // { 233 | // removal_start := get_time(timer); 234 | // for (i := 0; i < NUM_ELEMS; i += 1) { 235 | // hashtable_remove(&my_table, i); 236 | // } 237 | // removal_end := get_time(timer); 238 | // time := (removal_end-removal_start); 239 | // print("My map removing % elements: %s\n", NUM_ELEMS, time); 240 | // } 241 | // } -------------------------------------------------------------------------------- /core/os.sif: -------------------------------------------------------------------------------- 1 | #include "core:os_windows.sif" 2 | 3 | enum File_Open_Flags { 4 | READ = 1 << 0; 5 | WRITE = 1 << 1; 6 | } 7 | 8 | proc read_entire_file(filepath: string, allocator: Allocator) : []byte { 9 | file: File; 10 | if (!file_open(filepath, &file, .READ, allocator)) { 11 | return .{}; 12 | } 13 | defer file_close(file); 14 | length: int; 15 | if (!file_length(file, &length)) { 16 | return .{}; 17 | } 18 | data := new_slice(byte, length, allocator); 19 | bytes_read: int; 20 | if (!file_read(file, data, &bytes_read)) { 21 | delete_slice(data, allocator); 22 | return .{}; 23 | } 24 | assert(bytes_read == data.count); 25 | return data; 26 | } 27 | 28 | proc write_entire_file(filepath: string, data: []byte, allocator: Allocator) : bool { 29 | file: File; 30 | if (!file_open(filepath, &file, .WRITE, allocator)) { 31 | return .{}; 32 | } 33 | defer file_close(file); 34 | bytes_written: int; 35 | if (!file_write(file, data, &bytes_written)) { 36 | return false; 37 | } 38 | assert(bytes_written == data.count); 39 | return true; 40 | } -------------------------------------------------------------------------------- /core/os_windows.sif: -------------------------------------------------------------------------------- 1 | #include "core:windows.sif" 2 | 3 | struct File { 4 | handle: HANDLE; 5 | } 6 | 7 | proc file_open(filepath: string, out_file: ^File, flags: File_Open_Flags, allocator: Allocator) : bool { 8 | success: bool; 9 | length: i32; 10 | filepath_wide := to_wide_string(filepath, allocator, &success, &length); 11 | if (!success) { 12 | return false; 13 | } 14 | defer free(filepath_wide, allocator); 15 | access_flags: i32; 16 | share_flags: i32; 17 | create_flags: i32; 18 | if (flags & .READ > 0) { 19 | access_flags |= FILE_GENERIC_READ; 20 | share_flags |= FILE_SHARE_READ; 21 | create_flags |= OPEN_EXISTING; 22 | } 23 | if (flags & .WRITE > 0) { 24 | access_flags |= FILE_GENERIC_WRITE; 25 | create_flags |= CREATE_ALWAYS; 26 | } 27 | 28 | file: File; 29 | file.handle = CreateFileW(filepath_wide, access_flags, share_flags, null, create_flags, FILE_ATTRIBUTE_NORMAL, null); 30 | if (file.handle == INVALID_HANDLE_VALUE) { 31 | return false; 32 | } 33 | out_file^ = file; 34 | return true; 35 | } 36 | 37 | proc file_close(file: File) : bool { 38 | if (CloseHandle(file.handle) == 0) { 39 | return false; 40 | } 41 | return true; 42 | } 43 | 44 | proc file_length(file: File, out_length: ^int) : bool { 45 | integer: LARGE_INTEGER; 46 | if (GetFileSizeEx(file.handle, &integer) == 0) { 47 | return false; 48 | } 49 | out_length^ = integer.QuadPart; 50 | return true; 51 | } 52 | 53 | proc file_read(file: File, data: []byte, out_bytes_read: ^int) : bool { 54 | num_read: u32; 55 | if (ReadFile(file.handle, data.data, cast(u32, data.count), &num_read, null) == 0) { 56 | return false; 57 | } 58 | out_bytes_read^ = cast(int, num_read); 59 | return true; 60 | } 61 | 62 | proc file_write(file: File, data: []byte, out_bytes_written: ^int) : bool { 63 | num_written: u32; 64 | if (WriteFile(file.handle, data.data, cast(u32, data.count), &num_written, null) == 0) { 65 | return false; 66 | } 67 | out_bytes_written^ = cast(int, num_written); 68 | return true; 69 | } 70 | 71 | proc delete_file(filepath: string, allocator: Allocator) : bool { 72 | success: bool; 73 | length: i32; 74 | filepath_wide := to_wide_string(filepath, allocator, &success, &length); 75 | if (!success) { 76 | return false; 77 | } 78 | defer free(filepath_wide, allocator); 79 | DeleteFileW(filepath_wide); 80 | return true; 81 | } -------------------------------------------------------------------------------- /core/random.sif: -------------------------------------------------------------------------------- 1 | #include "core:math.sif" 2 | 3 | struct Random_State { 4 | state: u64; 5 | } 6 | 7 | proc init_random_state(r: ^Random_State, seed: u64) { 8 | r.state = seed; 9 | } 10 | 11 | proc _random_fnv(data: u64) : u64 { 12 | // todo(josh): can we just do the whole u64 in one go rather than having a loop? 13 | h : u64 = 0xcbf29ce484222325; 14 | key_slice := slice_ptr(cast(^byte, &data), sizeof(u64)); 15 | for (i := 0; i < sizeof(u64); i += 1) { 16 | h = (h * 0x100000001b3) ~ cast(u64, key_slice[i]); 17 | } 18 | return h; 19 | } 20 | 21 | proc _random_next(r: ^Random_State) : u32 { 22 | r.state = _random_fnv(r.state); 23 | return cast(u32, r.state); 24 | } 25 | 26 | proc random_u32(r: ^Random_State) : u32 { 27 | return _random_next(r); 28 | } 29 | proc random_u64(r: ^Random_State) : u64 { 30 | a := cast(u64, _random_next(r)); 31 | b := cast(u64, _random_next(r)); 32 | return (a<<32) | b; 33 | } 34 | 35 | proc random_i31(r: ^Random_State) : i32 { 36 | return cast(i32, random_u32(r) << 1 >> 1); 37 | } 38 | proc random_i63(r: ^Random_State) : i64 { 39 | return cast(i64, random_u64(r) << 1 >> 1); 40 | } 41 | 42 | proc random_i31_max(r: ^Random_State, n: i32) : i32 { 43 | if (n <= 0) { 44 | print("Invalid argument to random_i31_max\n"); 45 | assert(false); 46 | } 47 | val := random_i31(r) % n; 48 | return val; 49 | } 50 | proc random_i63_max(r: ^Random_State, n: i64) : i64 { 51 | if (n <= 0) { 52 | print("Invalid argument to random_i63_max\n"); 53 | assert(false); 54 | } 55 | val := random_i63(r) % n; 56 | return val; 57 | } 58 | 59 | proc random_f64(r: ^Random_State) : f64 { 60 | return cast(f64, random_i63_max(r, 1<<53)) / (1 << 53); 61 | } 62 | proc random_f32(r: ^Random_State) : f32 { 63 | return cast(f32, random_f64(r)); 64 | } 65 | 66 | proc random_f64_range(r: ^Random_State, lo: f64, hi: f64) : f64 { 67 | return (hi-lo)*random_f64(r) + lo; 68 | } 69 | proc random_f32_range(r: ^Random_State, lo: f32, hi: f32) : f32 { 70 | return (hi-lo)*random_f32(r) + lo; 71 | } -------------------------------------------------------------------------------- /core/runtime.sif: -------------------------------------------------------------------------------- 1 | // todo(josh): remove this dependency 2 | #include "core:basic.sif" 3 | 4 | proc assert (condition: bool) #foreign "sif"; @sif_runtime 5 | proc memset (ptr: rawptr, val: i32, size_in_bytes: u64) : rawptr #foreign "sif"; @sif_runtime 6 | proc memcpy (dst: rawptr, src: rawptr, size_in_bytes: u64) : rawptr #foreign "sif"; @sif_runtime 7 | proc memmove(dst: rawptr, src: rawptr, size_in_bytes: u64) : rawptr #foreign "sif"; @sif_runtime 8 | proc memcmp (ptr1: rawptr, ptr2: rawptr, n: uint) : i32 #foreign "sif"; @sif_runtime 9 | 10 | 11 | 12 | union Union_All_Type_Infos { 13 | type_info: Type_Info; 14 | type_info_integer: Type_Info_Integer; 15 | type_info_float: Type_Info_Float; 16 | type_info_bool: Type_Info_Bool; 17 | type_info_string: Type_Info_String; 18 | type_info_struct_field: Type_Info_Struct_Field; 19 | type_info_struct: Type_Info_Struct; 20 | type_info_union: Type_Info_Union; 21 | type_info_enum_field: Type_Info_Enum_Field; 22 | type_info_enum: Type_Info_Enum; 23 | type_info_pointer: Type_Info_Pointer; 24 | type_info_slice: Type_Info_Slice; 25 | type_info_array: Type_Info_Array; 26 | type_info_reference: Type_Info_Reference; 27 | type_info_procedure: Type_Info_Procedure; 28 | type_info_typeid: Type_Info_Typeid; 29 | } @sif_runtime 30 | 31 | struct Source_Code_Location { 32 | filepath: string; 33 | line: int; 34 | character: int; 35 | } @sif_runtime 36 | 37 | proc make_source_code_location(filepath: string, line: int, character: int) : Source_Code_Location { 38 | loc: Source_Code_Location; 39 | loc.filepath = filepath; 40 | loc.line = line; 41 | loc.character = character; 42 | return loc; 43 | } @sif_runtime 44 | 45 | proc sif_null_check(ptr: rawptr, location: Source_Code_Location) { 46 | if (ptr == null) { 47 | // todo(josh): remove this dependency on basic 48 | print("%(%:%): Null check failed: Attempted to dereference a null pointer.\n", location.filepath, location.line, location.character); 49 | assert(false); 50 | } 51 | } @sif_runtime 52 | 53 | proc sif_bounds_check(count: int, index: int, location: Source_Code_Location) { 54 | if (index < 0 || index >= count) { 55 | // todo(josh): remove this dependency on basic 56 | print("%(%:%): Bounds check failed: Index % is out of bounds 0..<%.\n", location.filepath, location.line, location.character, index, count); 57 | assert(false); 58 | } 59 | } @sif_runtime 60 | 61 | 62 | 63 | proc zero_pointer(ptr: rawptr, size_in_bytes: int) { 64 | memset(ptr, 0, cast(u64, size_in_bytes)); 65 | } @sif_runtime 66 | 67 | proc zero_memory(memory: []byte) { 68 | zero_pointer(memory.data, memory.count); 69 | } @sif_runtime 70 | 71 | 72 | 73 | proc ptr_offset(ptr: ^$T, offset_count: int) : ^T { 74 | ptr_int := transmute(u64, ptr); 75 | ptr_int += cast(u64, offset_count * sizeof(T)); 76 | return transmute(^T, ptr_int); 77 | } @sif_runtime 78 | 79 | 80 | 81 | proc slice_ptr(ptr: ^$T, count: int) : []T { 82 | slice: []T; 83 | slice.data = ptr; 84 | slice.count = count; 85 | return slice; 86 | } @sif_runtime 87 | 88 | proc string_ptr(ptr: rawptr, count: int) : string { 89 | str: string; 90 | str.data = cast(^u8, ptr); 91 | str.count = count; 92 | return str; 93 | } @sif_runtime 94 | 95 | 96 | 97 | proc to_slice(arr: ^[$N]$E) : []E { 98 | slice: []E; 99 | slice.data = &arr^[0]; 100 | slice.count = N; 101 | return slice; 102 | } @sif_runtime 103 | 104 | 105 | 106 | struct Raw_Slice { 107 | data: rawptr; 108 | count: i64; 109 | } @sif_runtime 110 | 111 | struct Raw_String { 112 | data: rawptr; 113 | count: i64; 114 | } @sif_runtime 115 | 116 | 117 | 118 | struct Allocator { 119 | data: rawptr; 120 | alloc_proc: proc(allocator: rawptr, size: int, align: int) : rawptr; 121 | free_proc: proc(allocator: rawptr, ptr: rawptr); 122 | } @sif_runtime 123 | 124 | proc alloc(size: int, align: int, allocator: Allocator) : rawptr { 125 | assert(allocator.alloc_proc != null); 126 | return allocator.alloc_proc(allocator.data, size, align); 127 | } @sif_runtime 128 | 129 | proc free(ptr: rawptr, allocator: Allocator) { 130 | assert(allocator.free_proc != null); 131 | allocator.free_proc(allocator.data, ptr); 132 | } @sif_runtime 133 | 134 | 135 | 136 | proc default_allocator_alloc(allocator: rawptr, size: int, align: int) : rawptr { 137 | proc malloc(size: uint) : rawptr #foreign "sif"; 138 | return malloc(cast(uint, size)); 139 | } @sif_runtime 140 | 141 | proc default_allocator_free(allocator: rawptr, ptr: rawptr) { 142 | proc free(ptr: rawptr) #foreign "sif"; 143 | free(ptr); 144 | } @sif_runtime 145 | 146 | proc default_allocator() : Allocator { 147 | a: Allocator; 148 | a.alloc_proc = default_allocator_alloc; 149 | a.free_proc = default_allocator_free; 150 | return a; 151 | } @sif_runtime 152 | 153 | 154 | 155 | proc string_eq(a: string, b: string) : bool { 156 | // if the counts are different they can't be equal 157 | if (a.count != b.count) { 158 | return false; 159 | } 160 | 161 | // if they are both zero count then they are equal 162 | if (a.count == 0) { 163 | return true; 164 | } 165 | 166 | // make sure they are both non-null 167 | if (a.data == null) { 168 | if (b.data == null) { 169 | return true; 170 | } 171 | return false; 172 | } 173 | else { 174 | if (b.data == null) { 175 | return false; 176 | } 177 | } 178 | 179 | return memcmp(a.data, b.data, cast(uint, a.count)) == 0; 180 | } @sif_runtime 181 | 182 | 183 | 184 | _global_type_table: []Union_All_Type_Infos @sif_runtime; 185 | 186 | proc get_type_info(type: typeid) : ^Type_Info { 187 | idx := transmute(u64, type); 188 | assert(transmute(u64, type) > 0); 189 | assert(transmute(u64, type) < cast(u64, _global_type_table.count)); 190 | return cast(^Type_Info, &_global_type_table[cast(int, idx)]); 191 | } @sif_runtime 192 | 193 | struct Type_Info { 194 | printable_name: string; 195 | kind: typeid; 196 | id: typeid; 197 | size: int; 198 | align: int; 199 | } @sif_runtime 200 | 201 | struct Type_Info_Integer { 202 | using base: Type_Info; 203 | is_signed: bool; 204 | } @sif_runtime 205 | 206 | struct Type_Info_Float { 207 | using base: Type_Info; 208 | } @sif_runtime 209 | 210 | struct Type_Info_Bool { 211 | using base: Type_Info; 212 | } @sif_runtime 213 | 214 | struct Type_Info_String { 215 | using base: Type_Info; 216 | is_cstring: bool; 217 | } @sif_runtime 218 | 219 | struct Type_Info_Struct_Field { 220 | name: string; 221 | type: ^Type_Info; 222 | offset: int; 223 | notes: []string; 224 | } @sif_runtime 225 | struct Type_Info_Struct { 226 | using base: Type_Info; 227 | name: string; 228 | fields: []Type_Info_Struct_Field; 229 | notes: []string; 230 | } @sif_runtime 231 | 232 | struct Type_Info_Union { 233 | using base: Type_Info; 234 | name: string; 235 | fields: []Type_Info_Struct_Field; 236 | notes: []string; 237 | } @sif_runtime 238 | 239 | struct Type_Info_Enum_Field { 240 | name: string; 241 | value: int; 242 | } @sif_runtime 243 | struct Type_Info_Enum { 244 | using base: Type_Info; 245 | base_type: ^Type_Info; 246 | fields: []Type_Info_Enum_Field; 247 | notes: []string; 248 | } @sif_runtime 249 | 250 | struct Type_Info_Pointer { 251 | using base: Type_Info; 252 | pointer_to: ^Type_Info; 253 | } @sif_runtime 254 | 255 | struct Type_Info_Slice { 256 | using base: Type_Info; 257 | slice_of: ^Type_Info; 258 | } @sif_runtime 259 | 260 | struct Type_Info_Array { 261 | using base: Type_Info; 262 | array_of: ^Type_Info; 263 | count: int; 264 | } @sif_runtime 265 | 266 | struct Type_Info_Reference { 267 | using base: Type_Info; 268 | reference_to: ^Type_Info; 269 | } @sif_runtime 270 | 271 | struct Type_Info_Procedure { 272 | using base: Type_Info; 273 | parameter_types: []^Type_Info; 274 | return_type: ^Type_Info; 275 | } @sif_runtime 276 | 277 | struct Type_Info_Typeid { 278 | using base: Type_Info; 279 | } @sif_runtime -------------------------------------------------------------------------------- /core/string_builder.sif: -------------------------------------------------------------------------------- 1 | struct String_Builder { 2 | buffer: ^u8; 3 | buffer_length: int; 4 | cursor: int; 5 | allocator: Allocator; 6 | } 7 | 8 | proc make_string_builder(length: int, allocator: Allocator) : String_Builder { 9 | sb: String_Builder; 10 | sb.buffer_length = length; 11 | sb.allocator = allocator; 12 | sb.buffer = cast(^u8, alloc(length, DEFAULT_ALIGNMENT, allocator)); 13 | return sb; 14 | } 15 | 16 | proc destroy_string_builder(sb: String_Builder) { 17 | free(sb.buffer, sb.allocator); 18 | } 19 | 20 | proc sbprint(sb: ^String_Builder, fmt: string, args: ..any) { 21 | length := bprint(slice_ptr(ptr_offset(sb.buffer, sb.cursor), max(0, sb.buffer_length - sb.cursor)), fmt, ..args); 22 | if (sb.cursor + length > sb.buffer_length) { 23 | old := sb.buffer; 24 | sb.buffer_length = (sb.cursor + length) * 2; 25 | sb.buffer = cast(^u8, alloc(sb.buffer_length, DEFAULT_ALIGNMENT, sb.allocator)); 26 | copy_slice(slice_ptr(sb.buffer, sb.buffer_length), slice_ptr(old, sb.cursor)); 27 | free(old, sb.allocator); 28 | length2 := bprint(slice_ptr(ptr_offset(sb.buffer, sb.cursor), max(0, sb.buffer_length - sb.cursor)), fmt, ..args); 29 | assert(length == length2); 30 | assert(sb.cursor + length2 <= sb.buffer_length); 31 | } 32 | sb.cursor += length; 33 | } 34 | 35 | proc sbprinta(sb: ^String_Builder, args: ..any) { 36 | length := bprinta(slice_ptr(ptr_offset(sb.buffer, sb.cursor), max(0, sb.buffer_length - sb.cursor)), ..args); 37 | if (sb.cursor + length > sb.buffer_length) { 38 | old := sb.buffer; 39 | sb.buffer_length = (sb.cursor + length) * 2; 40 | sb.buffer = cast(^u8, alloc(sb.buffer_length, DEFAULT_ALIGNMENT, sb.allocator)); 41 | copy_slice(slice_ptr(sb.buffer, sb.buffer_length), slice_ptr(old, sb.cursor)); 42 | free(old, sb.allocator); 43 | length2 := bprinta(slice_ptr(ptr_offset(sb.buffer, sb.cursor), max(0, sb.buffer_length - sb.cursor)), ..args); 44 | assert(length == length2); 45 | assert(sb.cursor + length2 <= sb.buffer_length); 46 | } 47 | sb.cursor += length; 48 | } 49 | 50 | proc string_builder_to_string(sb: String_Builder) : string { 51 | return string.{sb.buffer, sb.cursor}; 52 | } 53 | -------------------------------------------------------------------------------- /core/strings.sif: -------------------------------------------------------------------------------- 1 | #include "core:dynamic_array.sif" 2 | 3 | proc string_split(str: string, delimiter: byte, allocator: Allocator) : []string { 4 | results: Dynamic_Array!(string); 5 | results.allocator = allocator; 6 | start := 0; 7 | for (i := 0; i < str.count; i += 1) { 8 | if (str[i] == delimiter) { 9 | append(&results, string_ptr(&str[start], i - start)); 10 | start = i + 1; 11 | } 12 | } 13 | if (str.count > 0) { 14 | append(&results, string_ptr(&str[start], str.count - start)); 15 | } 16 | if (results.count > 0) { 17 | return slice_ptr(&results[0], results.count); 18 | } 19 | else { 20 | return .{}; 21 | } 22 | } 23 | 24 | proc index_of(str: string, c: byte, out_index: ^int) : bool { 25 | for (i := 0; i < str.count; i += 1) { 26 | if (str[i] == c) { 27 | out_index^ = i; 28 | return true; 29 | } 30 | } 31 | return false; 32 | } 33 | 34 | proc last_index_of(str: string, c: byte, out_index: ^int) : bool { 35 | for (i := str.count-1; i >= 0; i -= 1) { 36 | if (str[i] == c) { 37 | out_index^ = i; 38 | return true; 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | proc string_starts_with(str: string, start: string) : bool { 45 | if (str.count < start.count) { 46 | return false; 47 | } 48 | if (start.count <= 0) { 49 | return false; 50 | } 51 | for (i := 0; i < start.count; i += 1) { 52 | if (str[i] != start[i]) { 53 | return false; 54 | } 55 | } 56 | return true; 57 | } 58 | 59 | proc string_ends_with(str: string, end: string) : bool { 60 | if (str.count < end.count) { 61 | return false; 62 | } 63 | if (end.count <= 0) { 64 | return false; 65 | } 66 | str_end := str.count-1; 67 | end_end := end.count-1; 68 | for (; end_end >= 0; ;) { 69 | if (str[str_end] != end[end_end]) { 70 | return false; 71 | } 72 | str_end -= 1; 73 | end_end -= 1; 74 | } 75 | return true; 76 | } 77 | 78 | proc concatenate(strings: []string, allocator: Allocator) : string { 79 | required_length := 0; 80 | for (i := 0; i < strings.count; i += 1) { 81 | required_length += strings[i].count; 82 | } 83 | if (required_length == 0) { 84 | return .{}; 85 | } 86 | str := new_string(required_length, allocator); 87 | cursor := 0; 88 | for (i := 0; i < strings.count; i += 1) { 89 | elem := strings[i]; 90 | if (elem.count == 0) { 91 | continue; 92 | } 93 | copy_slice(transmute([]byte, slice_ptr(&str[cursor], str.count-cursor)), transmute([]byte, elem)); 94 | cursor += elem.count; 95 | } 96 | return str; 97 | } 98 | 99 | proc string_replace_in_place(str: string, replace: byte, with: byte) : int { 100 | num_replacements := 0; 101 | for (i := 0; i < str.count; i += 1) { 102 | if (str[i] == replace) { 103 | str[i] = with; 104 | num_replacements += 1; 105 | } 106 | } 107 | return num_replacements; 108 | } 109 | 110 | proc string_replace_new(str: string, replace: byte, with: byte, allocator: Allocator) : int { 111 | str = clone_string(str, allocator); 112 | return string_replace_in_place(str, replace, with); 113 | } -------------------------------------------------------------------------------- /core/time.sif: -------------------------------------------------------------------------------- 1 | #include "core:windows.sif" 2 | 3 | struct Timer { 4 | start_time: f64; 5 | frequency: f64; 6 | } 7 | 8 | proc init_timer(t: ^Timer) { 9 | result: i64; 10 | assert(QueryPerformanceFrequency(&result) != 0); 11 | assert(result != 0); 12 | t.frequency = cast(f64, result); 13 | assert(t.frequency != 0); 14 | t.start_time = get_time(t^); 15 | } 16 | 17 | proc get_time(t: Timer) : f64 { 18 | ticks: i64; 19 | ok := QueryPerformanceCounter(&ticks); 20 | assert(ok != 0); 21 | result := (cast(f64, ticks) / t.frequency) - t.start_time; 22 | return result; 23 | } -------------------------------------------------------------------------------- /examples/demo/main.sif: -------------------------------------------------------------------------------- 1 | #include "core:basic.sif" 2 | 3 | proc main() { 4 | print("-------------------------\n"); 5 | print("| sif language demo |\n"); 6 | print("-------------------------\n"); 7 | 8 | basic_stuff(); 9 | arrays(); 10 | slices(); 11 | strings(); 12 | structs(); 13 | enums(); 14 | order_independence(); 15 | varargs(); 16 | references(); 17 | function_pointers(); 18 | 19 | // cool stuff starts here 20 | operator_overloading(); 21 | procedural_polymorphism(); 22 | structural_polymorphism(); 23 | runtime_type_information(); 24 | using_statement(); 25 | defer_statement(); 26 | any_type(); 27 | dynamic_arrays(); 28 | } 29 | 30 | 31 | 32 | proc basic_stuff() { 33 | print("\n\n---- basic_stuff ----\n"); 34 | 35 | // declaration syntax 36 | // : = ; 37 | a1: int = 123; 38 | print("%\n", a1); 39 | 40 | // either or can be omitted, but not both 41 | a2: int; 42 | a3 := 2; 43 | 44 | // if no value is given, the memory will be zeroed 45 | b1: int; 46 | assert(b1 == 0); 47 | 48 | // if the type is omitted, it can be inferred from the right-hand-side expression 49 | b2 := 456; 50 | assert(typeof(b2) == int); 51 | print("%\n", b2); 52 | b3 := 456.0; 53 | assert(typeof(b3) == float); 54 | print("%\n", b3); 55 | 56 | 57 | 58 | // Local procedures, structs, and enums are allowed. 59 | proc factorial(n: int) : int { 60 | if (n == 1) { 61 | return 1; 62 | } 63 | return n * factorial(n-1); 64 | } 65 | 66 | // since factorial() returns an int, the type can be inferred here too 67 | fact := factorial(5); 68 | print("%\n", fact); 69 | assert(fact == 120); 70 | assert(typeof(fact) == int); 71 | 72 | // number literals are "untyped" and can coerce when needed 73 | c: int = 12; 74 | d: float = 12; 75 | 76 | // even when nested under binary expressions 77 | e: float = 11 / 2; 78 | assert(e == 5.5); 79 | 80 | // without type inference the "untyped" numbers default to int, as above 81 | f := 11 / 2; 82 | assert(f == 5); 83 | 84 | // if a `.` is present, float will be assumed, as above 85 | g := 11.0 / 2; 86 | assert(g == 5.5); 87 | } 88 | 89 | 90 | 91 | proc arrays() { 92 | print("\n\n---- arrays ----\n"); 93 | my_array: [4]int; 94 | my_array[0] = 1; 95 | my_array[1] = 2; 96 | my_array[2] = 3; 97 | my_array[3] = 4; 98 | print("%\n", my_array); 99 | 100 | // As with structs, compound literals work with arrays 101 | my_array = .{4, 3, 2, 1}; 102 | print("%\n", my_array); 103 | 104 | // Arrays are value types, so calling a function that changes the array 105 | // parameter will not affect the array at the call-site 106 | proc arrays_by_value(arr: [4]int) { 107 | arr[0] = 123; 108 | arr[1] = 345; 109 | arr[2] = 567; 110 | arr[3] = 789; 111 | } 112 | 113 | arrays_by_value(my_array); 114 | print("%\n", my_array); 115 | assert(my_array[0] == 4); 116 | assert(my_array[1] == 3); 117 | assert(my_array[2] == 2); 118 | assert(my_array[3] == 1); 119 | } 120 | 121 | 122 | 123 | proc slices() { 124 | print("\n\n---- slices ----\n"); 125 | 126 | array: [4]int; 127 | array[2] = 124; 128 | print("%\n", array[2]); 129 | slice := slice_ptr(&array[0], array.count); 130 | assert(slice[2] == 124); 131 | slice[2] = 421; 132 | print("%\n", slice[2]); 133 | assert(array[2] == 421); 134 | } 135 | 136 | 137 | 138 | proc strings() { 139 | print("\n\n---- strings ----\n"); 140 | 141 | // strings in sif are length-delimited rather than null-terminated. 142 | // the string type is implemented as: 143 | // struct String { 144 | // data: rawptr; 145 | // count: int; 146 | // } 147 | assert(sizeof(string) == 16); 148 | 149 | // since strings are length-delimited this means you can trivially 150 | // take a substring without allocating and copying 151 | a := "Hello, World!"; 152 | hello: string; 153 | hello.data = &a[0]; 154 | hello.count = 5; 155 | assert(hello == "Hello"); 156 | print("%\n", hello); 157 | 158 | world: string; 159 | world.data = &a[7]; 160 | world.count = 5; 161 | assert(world == "World"); 162 | print("%\n", world); 163 | 164 | // todo(josh): cstrings 165 | } 166 | 167 | 168 | 169 | proc structs() { 170 | print("\n\n---- structs ----\n"); 171 | 172 | struct Foo { 173 | a: int; 174 | str: string; 175 | t: bool; 176 | } 177 | 178 | f: Foo; 179 | assert(f.a == 0); // everything is zero initialized by default 180 | f.a = 123; 181 | f.str = "foozle"; 182 | f.t = f.a * 2 == 246; 183 | 184 | // Can also use compound literals to initialize 185 | f = Foo.{149, "hellooo", false}; 186 | 187 | // The type of the compound literal can be inferred from context. 188 | // Since the compiler knows the type of 'f', you don't have to specify 189 | // it in the compound literal. 190 | f = .{149, "hellooo", false}; 191 | 192 | // using runtime type information, you can print whole structs, more on that below. 193 | print("%\n", f); 194 | 195 | // Type inference also works when calling procedures, since the target type is known 196 | takes_a_foo(.{123, "wow", true}); 197 | 198 | proc takes_a_foo(foo: Foo) { 199 | print("%\n", foo); 200 | } 201 | } 202 | 203 | 204 | 205 | proc enums() { 206 | print("\n\n---- enums ----\n"); 207 | 208 | enum My_Enum { 209 | FOO; 210 | BAR; 211 | BAZ; 212 | } 213 | 214 | f := My_Enum.FOO; 215 | print("%\n", f); 216 | 217 | // If the enum type can be inferred from context, you don't have to 218 | // specify it on the right-hand-side. 219 | b: My_Enum = .BAR; // implicit enum selection 220 | print("%\n", b); 221 | 222 | // Implicit enum selection also works in binary expressions 223 | if (b == .BAR) { 224 | 225 | } 226 | 227 | // Note: the following will NOT work because the compiler can't 228 | // figure out the enum type to search for the fields FOO and BAR. 229 | // if (.FOO == .BAR) { 230 | // } 231 | 232 | // If at least one of them supplies the target enum, all good. 233 | if (.FOO == My_Enum.BAR) { 234 | } 235 | 236 | // As with compound literals, the type can also be inferred from 237 | // procedure parameters, allowing you to omit the enum name. 238 | takes_my_enum(.BAZ); 239 | 240 | proc takes_my_enum(e: My_Enum) { 241 | print("%\n", e); 242 | } 243 | } 244 | 245 | 246 | 247 | proc order_independence() { 248 | print("\n\n---- order_independence ----\n"); 249 | 250 | // declaration order does not matter. the compiler figures 251 | // out what depends on what for you. delete all your .h files :) 252 | 253 | loopy: Loopy; 254 | print("%\n", sizeof(typeof(loopy.a))); 255 | print("%\n", sizeof(typeof(nesty))); 256 | assert(sizeof(typeof(loopy.a)) == 8); 257 | assert(sizeof(typeof(nesty)) == 64); 258 | } 259 | nesty: [sizeof(typeof(ptr_to_loopy^))]int; 260 | ptr_to_loopy: ^Loopy; 261 | struct Loopy { 262 | a: [sizeof(typeof(ptr_to_loopy))]bool; 263 | } 264 | 265 | 266 | 267 | proc varargs() { 268 | print("\n\n---- varargs ----\n"); 269 | 270 | proc procedure_with_varargs(numbers: ..int) { 271 | print("varargs count: %\n", numbers.count); 272 | for (i := 0; i < numbers.count; i += 1) { 273 | print(" %\n", numbers[i]); 274 | } 275 | } 276 | 277 | procedure_with_varargs(1, 2, 3, 4); 278 | procedure_with_varargs(5, 6); 279 | procedure_with_varargs(); 280 | } 281 | 282 | 283 | 284 | proc references() { 285 | print("\n\n---- references ----\n"); 286 | 287 | my_int := 123; 288 | print("%\n", my_int); 289 | assert(my_int == 123); 290 | 291 | // reference implicitly take/dereference pointers depending on context 292 | // and are (currently, will likely change) denoted by a `>` before the type 293 | int_reference: >int = my_int; // implicit `&my_int` after `=` 294 | int_reference = 789; // implicit `int_reference^` before `=` 295 | assert(int_reference == 789); 296 | assert(my_int == 789); 297 | print("%\n", int_reference); 298 | 299 | // you can also define procedures that take (and return) references, of course 300 | proc change_by_reference(a: >int, value: int) { 301 | // since `a` is a reference, it is implicitly a pointer 302 | // and dereferences/indirections are inserted automatically 303 | a = value; 304 | } 305 | 306 | assert(my_int == 789); 307 | change_by_reference(my_int, 456); 308 | assert(my_int == 456); 309 | print("%\n", my_int); 310 | 311 | // the only real reason I implemented references is for overloading [] 312 | // which you can see below in the structural_polymorphism() and 313 | // dynamic_array() procedures 314 | } 315 | 316 | 317 | 318 | proc runtime_type_information() { 319 | print("\n\n---- runtime_type_information ----\n"); 320 | 321 | // Every type in your program gets it's own Type_Info struct that holds 322 | // information about that type, depending on the kind of type it is. If 323 | // it's a struct it has information about all the fields, if it is an 324 | // array it has the count and type that it is an array of, etc. 325 | // you can view all of the Type_Info structs in core/runtime.sif. 326 | 327 | 328 | 329 | // Using get_type_info() you can get information about a type. 330 | // The thing returned from get_type_info() is a ^Type_Info, which contains 331 | // a field `kind` indicating what _kind_ of type it is, such as INTEGER, 332 | // FLOAT, STRUCT, PROCEDURE, etc. Type_Info just contains the data relevant 333 | // to all types, such as size and alignment. To get more detailed information 334 | // about a type, check the `kind` field and cast the ^Type_Info to the appropriate 335 | // derived type: 336 | // ^Type_Info_Integer if `kind` is INTEGER 337 | // ^Type_Info_Struct if `kind` is STRUCT 338 | // ^Type_Info_Procedure if `kind` is PROCEDURE 339 | // etc. 340 | // Again, you can view all of the Type_Info structs in core/runtime.sif. 341 | 342 | // Type_Info_Integer just has a bool indicating whether it's signed or unsigned 343 | int_type_info: ^Type_Info = get_type_info(int); 344 | assert(int_type_info.kind == Type_Info_Integer); 345 | int_type_info_kind := cast(^Type_Info_Integer, int_type_info); 346 | assert(int_type_info_kind.is_signed == true); 347 | 348 | uint_type_info: ^Type_Info = get_type_info(uint); 349 | assert(uint_type_info.kind == Type_Info_Integer); 350 | uint_type_info_kind := cast(^Type_Info_Integer, uint_type_info); 351 | assert(uint_type_info_kind.is_signed == false); 352 | 353 | 354 | 355 | // Type_Info_Struct is a lot more interesting. It contains a list of all the fields, 356 | // and their types, and their byte offsets 357 | 358 | struct My_Cool_Struct { 359 | foo: int; 360 | bar: string; 361 | nested: Nested_Struct; 362 | } 363 | struct Nested_Struct { 364 | more_things: [4]Vector3; 365 | } 366 | 367 | my_cool_struct_ti := get_type_info(My_Cool_Struct); 368 | assert(my_cool_struct_ti.kind == Type_Info_Struct); 369 | my_cool_struct_ti_kind := cast(^Type_Info_Struct, my_cool_struct_ti); 370 | print("My_Cool_Struct\n"); 371 | for (i := 0; i < my_cool_struct_ti_kind.fields.count; i += 1) { 372 | field := my_cool_struct_ti_kind.fields[i]; 373 | print(" field: %, type: %, offset: %\n", 374 | field.name, field.type.printable_name, field.offset); 375 | } 376 | 377 | 378 | 379 | // using runtime type information, the print() function can print whole 380 | // structs intelligently. you can view the implementation of print() 381 | // in core/basic.sif 382 | print("%\n", int_type_info_kind^); 383 | // prints: Type_Info_Integer{base = Type_Info{printable_name = "i64", 384 | // kind = Type_Info_Kind.INTEGER, id = i64, size = 8, align = 8}, 385 | // is_signed = true} 386 | } 387 | 388 | 389 | 390 | proc using_statement() { 391 | print("\n\n---- using_statement ----\n"); 392 | 393 | // 'using' pulls a namespace's declarations into the namespace 394 | // of the using. 395 | 396 | enum My_Enum_For_Using { 397 | FOO; 398 | BAR; 399 | BAZ; 400 | } 401 | 402 | // For example if we wanted to print the elements from My_Enum_For_Using 403 | // you would have to do it like this: 404 | print("%\n", My_Enum_For_Using.FOO); 405 | print("%\n", My_Enum_For_Using.BAR); 406 | print("%\n", My_Enum_For_Using.BAZ); 407 | 408 | // But if you didn't want to do all that typing all the time, you could 409 | // 'using' that enum: 410 | using My_Enum_For_Using; 411 | print("%\n", FOO); 412 | print("%\n", BAR); 413 | print("%\n", BAZ); 414 | 415 | // You can use 'using' inside structs as well, to get a form of subtyping 416 | // that is more flexible than conventional inheritance. 417 | 418 | struct My_Struct_With_Using { 419 | using other: My_Other_Struct; 420 | } 421 | struct My_Other_Struct { 422 | some_field: int; 423 | } 424 | 425 | my_struct: My_Struct_With_Using; 426 | my_struct.some_field = 321; // no need to write 'my_struct.other.some_field' 427 | print("%\n", my_struct.some_field); 428 | assert(my_struct.some_field == my_struct.other.some_field); 429 | 430 | using my_struct; 431 | print("%\n", some_field); // no need to write 'my_struct.other.some_field' 432 | assert(some_field == my_struct.other.some_field); 433 | 434 | // You can 'using' as many fields as you'd like, provided that the names 435 | // do not collide. 436 | 437 | // 'using' in procedure parameters works too 438 | proc a_proc_with_using(using x: My_Struct_With_Using, param: int) { 439 | print("%\n", some_field + param); 440 | } 441 | 442 | a_proc_with_using(my_struct, 2); 443 | a_proc_with_using(my_struct, 4); 444 | } 445 | 446 | 447 | 448 | proc defer_statement() { 449 | print("\n\n---- defer_statement ----\n"); 450 | 451 | // 'defer' executes a statement at the end of the current block 452 | { 453 | a := 123; 454 | { 455 | defer a = 321; 456 | assert(a == 123); 457 | } 458 | assert(a == 321); 459 | } 460 | 461 | // a good use-case for defer is memory management, keeping the 462 | // freeing of resources nearby the acquision site 463 | { 464 | some_allocated_memory := new_slice(int, 16, default_allocator()); 465 | defer delete_slice(some_allocated_memory, default_allocator()); 466 | 467 | // ... do a bunch of work with some_allocated_memory 468 | } 469 | 470 | // if there are multiple defers, they are executed in reverse order 471 | { 472 | defer print("Will print third\n"); 473 | defer print("Will print second\n"); 474 | defer print("Will print first\n"); 475 | } 476 | 477 | // in addition to automatically running at the end of blocks, normal 478 | // control flow statements like 'break', 'continue', and 'return' also 479 | // invoke defers 480 | { 481 | a := 123; 482 | for (i := 0; i < 10; i += 1) { 483 | if (i == 7) { 484 | defer a = 321; 485 | break; 486 | } 487 | assert(a == 123); 488 | } 489 | assert(a == 321); 490 | } 491 | } 492 | 493 | 494 | 495 | struct Vector3 { 496 | x: float; 497 | y: float; 498 | z: float; 499 | operator +(a: Vector3, b: Vector3) : Vector3 { 500 | return Vector3.{a.x + b.x, a.y + b.y, a.z + b.z}; 501 | } 502 | operator -(a: Vector3, b: Vector3) : Vector3 { 503 | return Vector3.{a.x - b.x, a.y - b.y, a.z - b.z}; 504 | } 505 | operator *(a: Vector3, b: Vector3) : Vector3 { 506 | return Vector3.{a.x * b.x, a.y * b.y, a.z * b.z}; 507 | } 508 | operator /(a: Vector3, b: Vector3) : Vector3 { 509 | return Vector3.{a.x / b.x, a.y / b.y, a.z / b.z}; 510 | } 511 | 512 | operator *(a: Vector3, f: float) : Vector3 { 513 | return Vector3.{a.x * f, a.y * f, a.z * f}; 514 | } 515 | } 516 | proc operator_overloading() { 517 | print("\n\n---- operator_overloading ----\n"); 518 | 519 | v1 := Vector3.{1, 2, 3}; 520 | v2 := Vector3.{1, 4, 9}; 521 | v3 := v1 + v2; 522 | v4 := v3 * 5; 523 | print("%\n", v4.x); 524 | print("%\n", v4.y); 525 | print("%\n", v4.z); 526 | assert(v4.x == 10); 527 | assert(v4.y == 30); 528 | assert(v4.z == 60); 529 | } 530 | 531 | 532 | 533 | proc procedural_polymorphism() { 534 | print("\n\n---- procedural_polymorphism ----\n"); 535 | 536 | // $ on the name of a parameter means this parameter MUST be constant 537 | // and a new polymorph of the procedure will be baked with that constant. 538 | // for example if you called `value_poly(4)` a new version of `value_poly` 539 | // would be generated that replaces each use of the parameter `a` with the 540 | // `4` that you passed. this means the return statement will constant fold 541 | // into a simple `return 16;` 542 | proc value_poly($a: int) : int { 543 | return a * a; 544 | } 545 | 546 | print("%\n", value_poly(2)); 547 | assert(value_poly(2) == 4); 548 | 549 | // $ on the type of a parameter means that this parameter can be ANY 550 | // type, and a new version of this procedure will be generated for 551 | // that type. for example `type_poly(124)` would generate a version 552 | // of `type_poly` where `T` is `int`. `type_poly(Vector3{1, 4, 9})` 553 | // would generate a version where `T` is `Vector3` 554 | proc type_poly(a: $T) : T { 555 | return a * a; 556 | } 557 | 558 | print("%\n", type_poly(3)); 559 | assert(type_poly(3) == 9); 560 | print_float(type_poly(4.0)); 561 | assert(type_poly(4.0) == 16); 562 | 563 | a: int = 5; 564 | f: float = 6; 565 | print("%\n", type_poly(a)); 566 | print("%\n", type_poly(f)); 567 | assert(type_poly(a) == 25); 568 | assert(type_poly(f) == 36); 569 | 570 | // $ on both the name and the type means that this procedure can accept 571 | // any type and it must be a constant. simply a combination of the two 572 | // cases above. 573 | proc value_and_type_poly($a: $T) : T { 574 | return a * a; 575 | } 576 | 577 | print("%\n", value_and_type_poly(7)); 578 | print("%\n", value_and_type_poly(8.0)); 579 | assert(value_and_type_poly(7) == 49); 580 | assert(value_and_type_poly(8.0) == 64); 581 | } 582 | 583 | 584 | 585 | proc structural_polymorphism() { 586 | print("\n\n---- structural_polymorphism ----\n"); 587 | 588 | // same with procedural polymorphism, a new version of the struct 589 | // Custom_Array_Type will be generated for each set of parameters 590 | // that are passed, creating a brand new type. 591 | // for structs, unlike procedures, these values must all be constant 592 | // values in order to maintain compile-time typesafety 593 | struct Custom_Array_Type!($N: int, $T: typeid) { 594 | array: [N]T; 595 | operator [](my_array: >Custom_Array_Type!(N, T), index: int) : >T { 596 | // implicitly takes a pointer to `my_array.array[index]` since this 597 | // procedure returns a reference 598 | return my_array.array[index]; 599 | } 600 | } 601 | 602 | array_of_ints: Custom_Array_Type!(8, int); 603 | array_of_ints[4] = 124; // calls [] operator overload 604 | print("%\n", array_of_ints[4]); 605 | assert(array_of_ints[4] == 124); 606 | } 607 | 608 | 609 | 610 | proc any_type() { 611 | print("\n\n---- any ----\n"); 612 | 613 | // an `any` is defined as the following: 614 | // struct Any { 615 | // data: rawptr; 616 | // type: typeid; 617 | // } 618 | // any type will implicitly convert to an `any` by taking a pointer to 619 | // the data and setting the type field to the appropriate typeid 620 | some_int := 123; 621 | a: any = some_int; 622 | assert(a.data == &some_int); 623 | assert(a.type == int); 624 | 625 | // you can access the data pointed to by an `any` by casting the data 626 | // pointer and dereferencing 627 | b: int = cast(^int, a.data)^; 628 | print("%\n", b); 629 | 630 | // the most notable use of `any` is for a print routine. here is an 631 | // example using varargs 632 | print_things(true, 456, 78.9, "hello"); 633 | 634 | proc print_things(args: ..any) { 635 | for (i := 0; i < args.count; i += 1) { 636 | arg := args[i]; 637 | if (arg.type == int) { 638 | print("%\n", cast(^int, arg.data)^); 639 | } 640 | else if (arg.type == string) { 641 | print("%\n", cast(^string, arg.data)^); 642 | } 643 | else if (arg.type == bool) { 644 | print("%\n", cast(^bool, arg.data)^); 645 | } 646 | else if (arg.type == float) { 647 | print("%\n", cast(^float, arg.data)^); 648 | } 649 | else { 650 | print("\n"); 651 | } 652 | } 653 | } 654 | } 655 | 656 | 657 | 658 | proc function_pointers() { 659 | print("\n\n---- any ----\n"); 660 | 661 | my_print1 := print; // uses type inference 662 | my_print1("This is a function pointer! %\n", "cool"); 663 | 664 | my_print2: proc(fmt: string, args: ..any) = print; // without type inference 665 | my_print2("This is also % a function % pointer\n", 45.6, true); 666 | } 667 | 668 | 669 | 670 | // the following is a barebones implementation of a resizable array 671 | // using many of the features you've seen thus far including 672 | // structural and procedural polymorphism and operator overloading 673 | 674 | struct Dynamic_Array!($T: typeid) { 675 | elements: []T; 676 | count: int; 677 | allocator: Allocator; 678 | operator [](dyn: >Dynamic_Array!(T), index: int) : >T { 679 | return dyn.elements[index]; 680 | } 681 | } 682 | 683 | proc append(dyn: ^Dynamic_Array!($T), value: T) : ^T { 684 | assert(dyn.allocator.alloc_proc != null); 685 | if (dyn.count == dyn.elements.count) { 686 | old_data := dyn.elements.data; 687 | new_cap := 8 + dyn.elements.count * 2; 688 | dyn.elements.data = cast(^T, alloc(new_cap * sizeof(T), DEFAULT_ALIGNMENT, dyn.allocator)); 689 | dyn.elements.count = new_cap; 690 | if (old_data != null) { 691 | memcpy(dyn.elements.data, old_data, cast(u64, dyn.count * sizeof(T))); 692 | free(old_data, dyn.allocator); 693 | } 694 | } 695 | assert(dyn.count < dyn.elements.count); 696 | dyn.elements[dyn.count] = value; 697 | ptr := &dyn.elements[dyn.count]; 698 | dyn.count += 1; 699 | return ptr; 700 | } 701 | 702 | proc pop(dyn: ^Dynamic_Array!($T)) : T { 703 | assert(dyn.count > 0); 704 | value := dyn[dyn.count-1]; 705 | dyn.count -= 1; 706 | return value; 707 | } 708 | 709 | proc clear_dynamic_array(dyn: ^Dynamic_Array!($T)) { 710 | dyn.count = 0; 711 | } 712 | 713 | proc destroy_dynamic_array(dyn: Dynamic_Array!($T)) { 714 | if (dyn.elements.data != null) { 715 | free(dyn.elements.data, dyn.allocator); 716 | } 717 | } 718 | 719 | proc dynamic_arrays() { 720 | print("\n\n---- dynamic_arrays ----\n"); 721 | 722 | dyn: Dynamic_Array!(Vector3); 723 | dyn.allocator = default_allocator(); 724 | append(&dyn, Vector3.{1, 2, 3}); 725 | append(&dyn, Vector3.{1, 4, 9}); 726 | append(&dyn, Vector3.{2, 8, 18}); 727 | assert(dyn[1].x == 1); 728 | assert(dyn[1].y == 4); 729 | assert(dyn[1].z == 9); 730 | for (i := 0; i < dyn.count; i += 1) { 731 | print("index %\n", i); 732 | print(" %\n", dyn[i].x); 733 | print(" %\n", dyn[i].y); 734 | print(" %\n", dyn[i].z); 735 | } 736 | destroy_dynamic_array(dyn); 737 | } -------------------------------------------------------------------------------- /examples/directx_win32_triangle/basic_pixel.hlsl: -------------------------------------------------------------------------------- 1 | #include "types.hlsl" 2 | 3 | SamplerState main_sampler; 4 | 5 | float4 main(PS_INPUT input) : SV_Target { 6 | float4 output_color = input.color; 7 | return output_color; 8 | } -------------------------------------------------------------------------------- /examples/directx_win32_triangle/basic_vertex.hlsl: -------------------------------------------------------------------------------- 1 | #include "types.hlsl" 2 | 3 | PS_INPUT main(VS_INPUT input) { 4 | PS_INPUT v; 5 | v.position = float4(input.position, 1.0); 6 | v.texcoord = input.texcoord; 7 | v.color = input.color; 8 | return v; 9 | } -------------------------------------------------------------------------------- /examples/directx_win32_triangle/main.sif: -------------------------------------------------------------------------------- 1 | #include "core:basic.sif" 2 | #include "core:windows.sif" 3 | #include "core:math.sif" 4 | #include "core:directx/d3d11.sif" 5 | 6 | struct Vertex { 7 | position: Vector3; 8 | uv: Vector3; 9 | color: Vector4; 10 | } 11 | 12 | proc main() { 13 | // 14 | window := create_window("My Cool Window", 1920, 1080); 15 | init_directx(&window); 16 | vertex_shader_blob: ^ID3D10Blob; 17 | vertex_shader := compile_vertex_shader_from_file("basic_vertex.hlsl", &vertex_shader_blob); 18 | pixel_shader := compile_pixel_shader_from_file("basic_pixel.hlsl"); 19 | 20 | // 21 | vertex_fields := [3]D3D11_INPUT_ELEMENT_DESC.{ 22 | .{"SV_POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, 23 | .{"TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}, 24 | .{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0} 25 | }; 26 | vertex_format: ^ID3D11InputLayout; 27 | result := directx_device.CreateInputLayout(directx_device, &vertex_fields[0], 3, vertex_shader_blob.GetBufferPointer(vertex_shader_blob), vertex_shader_blob.GetBufferSize(vertex_shader_blob), &vertex_format); 28 | assert(result == S_OK); 29 | vertex_shader_blob.Release(vertex_shader_blob); 30 | 31 | // 32 | vertices := [3]Vertex.{ 33 | .{.{-0.5, -0.5, 0}, .{0, 0, 0}, .{0.25, 0.5, 1, 1}}, 34 | .{.{0.5, -0.5, 0}, .{0, 0, 0}, .{0.25, 0.5, 1, 1}}, 35 | .{.{0, 0.5, 0}, .{0, 0, 0}, .{0.25, 0.5, 1, 1}} 36 | }; 37 | vertex_buffer := create_vertex_buffer(&vertices[0], sizeof(typeof(vertices))); 38 | 39 | // 40 | while (!window.should_close) { 41 | update_window(&window); 42 | 43 | ensure_swap_chain_size(window.width, window.height); 44 | 45 | bind_shaders(vertex_shader, pixel_shader); 46 | directx_device_context.IASetInputLayout(directx_device_context, vertex_format); 47 | 48 | directx_device_context.OMSetDepthStencilState(directx_device_context, depth_test_state, 0); 49 | directx_device_context.RSSetState(directx_device_context, no_cull_rasterizer); 50 | directx_device_context.PSSetSamplers(directx_device_context, 0, 1, &linear_wrap_sampler); 51 | blend_factor := [4]FLOAT.{1, 1, 1, 1}; 52 | directx_device_context.OMSetBlendState(directx_device_context, alpha_blend_state, &blend_factor[0], 0xffffffff); 53 | 54 | viewport: D3D11_VIEWPORT; 55 | viewport.TopLeftX = 0; 56 | viewport.TopLeftY = 0; 57 | viewport.Width = cast(f32, window.width); 58 | viewport.Height = cast(f32, window.height); 59 | viewport.MinDepth = 0; 60 | viewport.MaxDepth = 1; 61 | directx_device_context.RSSetViewports(directx_device_context, 1, &viewport); 62 | 63 | directx_device_context.IASetPrimitiveTopology(directx_device_context, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 64 | 65 | directx_device_context.OMSetRenderTargets(directx_device_context, 1, &swap_chain_rtv, null); 66 | 67 | clear_color := [4]float.{1, 0.5, 0.2, 1}; 68 | directx_device_context.ClearRenderTargetView(directx_device_context, swap_chain_rtv, &clear_color[0]); 69 | 70 | stride: u32 = sizeof(Vertex); 71 | offset: u32 = 0; 72 | bind_vertex_buffers(&vertex_buffer, 1, 0, &stride, &offset); 73 | 74 | directx_device_context.Draw(directx_device_context, 3, 0); 75 | 76 | swap_chain_handle.Present(swap_chain_handle, 1, 0); 77 | } 78 | } 79 | 80 | 81 | 82 | directx_device: ^ID3D11Device; 83 | directx_device_context: ^ID3D11DeviceContext; 84 | swap_chain_handle: ^IDXGISwapChain; 85 | swap_chain_rtv: ^ID3D11RenderTargetView; 86 | swap_chain_width: int; 87 | swap_chain_height: int; 88 | no_cull_rasterizer: ^ID3D11RasterizerState; 89 | depth_test_state: ^ID3D11DepthStencilState; 90 | linear_wrap_sampler: ^ID3D11SamplerState; 91 | alpha_blend_state: ^ID3D11BlendState; 92 | 93 | const SWAP_CHAIN_BUFFER_COUNT := 2; 94 | const SWAP_CHAIN_FORMAT := DXGI_FORMAT_R8G8B8A8_UNORM; 95 | 96 | proc init_directx(window: ^Window) { 97 | // Create swap chain 98 | swap_chain_desc: DXGI_SWAP_CHAIN_DESC; 99 | swap_chain_desc.BufferCount = SWAP_CHAIN_BUFFER_COUNT; 100 | swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // todo(josh): use DXGI_SWAP_EFFECT_DISCARD (or something else) on non-Windows 10 101 | swap_chain_desc.BufferDesc.Width = cast(u32, window.width); 102 | swap_chain_desc.BufferDesc.Height = cast(u32, window.height); 103 | swap_chain_desc.BufferDesc.Format = SWAP_CHAIN_FORMAT; 104 | swap_chain_desc.BufferDesc.RefreshRate.Numerator = 60; // todo(josh): query monitor refresh rate. 105 | swap_chain_desc.BufferDesc.RefreshRate.Denominator = 1; 106 | swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 107 | swap_chain_desc.OutputWindow = window.handle; 108 | swap_chain_desc.SampleDesc.Count = 1; 109 | swap_chain_desc.SampleDesc.Quality = 0; 110 | swap_chain_desc.Windowed = .TRUE; 111 | 112 | requested_feature_level: D3D_FEATURE_LEVEL = D3D_FEATURE_LEVEL_11_0; 113 | actual_feature_level: D3D_FEATURE_LEVEL; 114 | result := D3D11CreateDeviceAndSwapChain( 115 | null, 116 | D3D_DRIVER_TYPE_HARDWARE, 117 | null, 118 | D3D11_CREATE_DEVICE_DEBUG, 119 | &requested_feature_level, 120 | 1, 121 | D3D11_SDK_VERSION, 122 | &swap_chain_desc, 123 | &swap_chain_handle, 124 | &directx_device, 125 | &actual_feature_level, 126 | &directx_device_context); 127 | assert(result == S_OK); 128 | 129 | create_swap_chain_render_target_view(); 130 | 131 | // create rasterizer 132 | no_cull_rasterizer_desc: D3D11_RASTERIZER_DESC; 133 | no_cull_rasterizer_desc.FillMode = D3D11_FILL_SOLID; 134 | no_cull_rasterizer_desc.CullMode = D3D11_CULL_NONE; 135 | result = directx_device.CreateRasterizerState(directx_device, &no_cull_rasterizer_desc, &no_cull_rasterizer); 136 | assert(result == S_OK); 137 | 138 | // create depth stencil state 139 | depth_test_stencil_desc: D3D11_DEPTH_STENCIL_DESC; 140 | depth_test_stencil_desc.DepthEnable = .TRUE; 141 | depth_test_stencil_desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; 142 | depth_test_stencil_desc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; 143 | depth_test_stencil_desc.StencilEnable = .TRUE; 144 | depth_test_stencil_desc.StencilReadMask = 0xff; 145 | depth_test_stencil_desc.StencilWriteMask = 0xff; 146 | depth_test_stencil_desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; 147 | depth_test_stencil_desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; 148 | depth_test_stencil_desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; 149 | depth_test_stencil_desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; 150 | depth_test_stencil_desc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; 151 | depth_test_stencil_desc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; 152 | depth_test_stencil_desc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; 153 | depth_test_stencil_desc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; 154 | result = directx_device.CreateDepthStencilState(directx_device, &depth_test_stencil_desc, &depth_test_state); 155 | assert(result == S_OK); 156 | 157 | // create linear wrap sampler 158 | linear_wrap_sampler_desc: D3D11_SAMPLER_DESC; 159 | linear_wrap_sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; 160 | linear_wrap_sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; 161 | linear_wrap_sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; 162 | linear_wrap_sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; 163 | linear_wrap_sampler_desc.MinLOD = -F32_MAX; 164 | linear_wrap_sampler_desc.MaxLOD = F32_MAX; 165 | result = directx_device.CreateSamplerState(directx_device, &linear_wrap_sampler_desc, &linear_wrap_sampler); 166 | assert(result == S_OK); 167 | 168 | 169 | // create alpha blend state 170 | alpha_blend_desc: D3D11_BLEND_DESC; 171 | alpha_blend_desc.RenderTarget[0].BlendEnable = .TRUE; 172 | alpha_blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; 173 | alpha_blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; 174 | alpha_blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; 175 | alpha_blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA; 176 | alpha_blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; 177 | alpha_blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; 178 | alpha_blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; 179 | result = directx_device.CreateBlendState(directx_device, &alpha_blend_desc, &alpha_blend_state); 180 | assert(result == S_OK); 181 | } 182 | 183 | proc create_swap_chain_render_target_view() { 184 | // create render target view from swap chain 185 | render_target_view_desc: D3D11_RENDER_TARGET_VIEW_DESC; 186 | render_target_view_desc.Format = SWAP_CHAIN_FORMAT; 187 | render_target_view_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; 188 | backing_texture: ^ID3D11Texture2D; 189 | texture_2d_uid := uid(ID3D11Texture2D.uuid); 190 | result := swap_chain_handle.GetBuffer(swap_chain_handle, 0, &texture_2d_uid, cast(^rawptr, &backing_texture)); 191 | assert(result == S_OK); 192 | result = directx_device.CreateRenderTargetView(directx_device, cast(^ID3D11Resource, backing_texture), &render_target_view_desc, &swap_chain_rtv); 193 | assert(result == S_OK); 194 | backing_texture.Release(backing_texture); 195 | } 196 | 197 | proc ensure_swap_chain_size(width: int, height: int) { 198 | assert(width > 0); 199 | assert(height > 0); 200 | if (swap_chain_width != width || swap_chain_height != height) { 201 | swap_chain_rtv.Release(swap_chain_rtv); 202 | result := swap_chain_handle.ResizeBuffers(swap_chain_handle, SWAP_CHAIN_BUFFER_COUNT, cast(u32, width), cast(u32, height), SWAP_CHAIN_FORMAT, 0); 203 | assert(result == S_OK); 204 | swap_chain_width = width; 205 | swap_chain_height = height; 206 | create_swap_chain_render_target_view(); 207 | } 208 | } 209 | 210 | proc create_vertex_buffer(data: rawptr, len: int) : ^ID3D11Buffer { 211 | buffer_desc: D3D11_BUFFER_DESC; 212 | buffer_desc.Usage = D3D11_USAGE_DEFAULT; 213 | buffer_desc.ByteWidth = cast(u32, len); 214 | buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; 215 | 216 | buffer_data: D3D11_SUBRESOURCE_DATA; 217 | buffer_data.pSysMem = data; 218 | 219 | ptr_buffer: ^D3D11_SUBRESOURCE_DATA; 220 | if (data != null) { 221 | ptr_buffer = &buffer_data; 222 | } 223 | buffer: ^ID3D11Buffer; 224 | result := directx_device.CreateBuffer(directx_device, &buffer_desc, ptr_buffer, &buffer); 225 | assert(result == S_OK); 226 | return buffer; 227 | } 228 | proc bind_vertex_buffers(buffers: ^^ID3D11Buffer, num_buffers: int, start_slot: u32, strides: ^u32, offsets: ^u32) { 229 | directx_device_context.IASetVertexBuffers(directx_device_context, start_slot, cast(u32, num_buffers), buffers, strides, offsets); 230 | } 231 | 232 | 233 | 234 | const D3D_SHADER_COMPILE_FLAGS := D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS; 235 | 236 | proc compile_vertex_shader_from_file(filename: string, out_blob: ^^ID3D10Blob) : ^ID3D11VertexShader { // todo(josh): use a temp allocator to go from char * to wchar_t * 237 | errors: ^ID3D10Blob; 238 | vertex_shader_blob: ^ID3D10Blob; 239 | success: bool; 240 | result_length_in_bytes: i32; 241 | filename_wide := to_wide_string(filename, default_allocator(), &success, &result_length_in_bytes); 242 | defer free(filename_wide, default_allocator()); 243 | result := D3DCompileFromFile(filename_wide, cast(rawptr, 0), cast(^ID3DInclude, 1), "main", "vs_5_0", D3D_SHADER_COMPILE_FLAGS, 0, &vertex_shader_blob, &errors); 244 | if (errors != null) { 245 | str := cast(cstring, errors.GetBufferPointer(errors)); 246 | print(string_ptr(str, cast(i64, strlen(str)))); 247 | assert(false); 248 | } 249 | assert(result == S_OK); 250 | vertex_shader_handle: ^ID3D11VertexShader; 251 | result = directx_device.CreateVertexShader(directx_device, vertex_shader_blob.GetBufferPointer(vertex_shader_blob), vertex_shader_blob.GetBufferSize(vertex_shader_blob), null, &vertex_shader_handle); 252 | assert(result == S_OK); 253 | if (errors != null) errors.Release(errors); 254 | out_blob^ = vertex_shader_blob; 255 | return vertex_shader_handle; 256 | } 257 | 258 | proc compile_pixel_shader_from_file(filename: string) : ^ID3D11PixelShader { 259 | errors: ^ID3D10Blob; 260 | pixel_shader_blob: ^ID3D10Blob; 261 | success: bool; 262 | result_length_in_bytes: i32; 263 | filename_wide := to_wide_string(filename, default_allocator(), &success, &result_length_in_bytes); 264 | defer free(filename_wide, default_allocator()); 265 | result := D3DCompileFromFile(filename_wide, cast(rawptr, 0), cast(^ID3DInclude, 1), "main", "ps_5_0", D3D_SHADER_COMPILE_FLAGS, 0, &pixel_shader_blob, &errors); 266 | if (errors != null) { 267 | str := cast(cstring, errors.GetBufferPointer(errors)); 268 | print(string_ptr(str, cast(i64, strlen(str)))); 269 | assert(false); 270 | } 271 | assert(result == S_OK); 272 | pixel_shader: ^ID3D11PixelShader; 273 | result = directx_device.CreatePixelShader(directx_device, pixel_shader_blob.GetBufferPointer(pixel_shader_blob), pixel_shader_blob.GetBufferSize(pixel_shader_blob), null, &pixel_shader); 274 | assert(result == S_OK); 275 | if (errors != null) errors.Release(errors); 276 | pixel_shader_blob.Release(pixel_shader_blob); 277 | return pixel_shader; 278 | } 279 | 280 | proc bind_shaders(vertex: ^ID3D11VertexShader, pixel: ^ID3D11PixelShader) { 281 | directx_device_context.VSSetShader(directx_device_context, vertex, null, 0); 282 | directx_device_context.PSSetShader(directx_device_context, pixel, null, 0); 283 | } 284 | 285 | 286 | 287 | struct Window { 288 | should_close: bool; 289 | 290 | width: int; 291 | height: int; 292 | aspect: float; 293 | size: Vector2; 294 | 295 | is_focused: bool; 296 | 297 | handle: HWND; 298 | dc: HDC; 299 | } 300 | 301 | proc create_window(name: string, width: int, height: int) : Window { 302 | const CLASS_NAME := "my window class"; 303 | 304 | wc: WNDCLASSEXW; 305 | wc.cbSize = sizeof(WNDCLASSEXW); 306 | wc.style = CS_OWNDC; 307 | wc.hCursor = LoadCursorW(null, cast(^u16, IDC_ARROW)); 308 | wc.lpfnWndProc = win32_proc; 309 | wc.hInstance = GetModuleHandleW(null); 310 | success: bool; 311 | string_length: i32; 312 | wc.lpszClassName = to_wide_string(CLASS_NAME, default_allocator(), &success, &string_length); 313 | class := RegisterClassExW(&wc); 314 | assert(class != 0); 315 | 316 | window: Window; 317 | window.width = width; 318 | window.height = height; 319 | assert(g_currently_processing_window == null); 320 | g_currently_processing_window = &window; 321 | window_handle := CreateWindowExW( 322 | 0, 323 | to_wide_string(CLASS_NAME, default_allocator(), &success, &string_length), 324 | to_wide_string("My Cool Window", default_allocator(), &success, &string_length), 325 | WS_OVERLAPPEDWINDOW | WS_VISIBLE, 326 | 300, 150, cast(i32, width), cast(i32, height), 327 | null, 328 | null, 329 | wc.hInstance, 330 | null 331 | ); 332 | 333 | assert(window_handle != null); 334 | dc := GetDC(window_handle); 335 | window.handle = window_handle; 336 | window.dc = dc; 337 | g_currently_processing_window = null; 338 | 339 | return window; 340 | } 341 | 342 | proc update_window(window: ^Window) { 343 | g_currently_processing_window = window; 344 | defer g_currently_processing_window = null; 345 | msg: MSG; 346 | while (PeekMessageW(&msg, null, 0, 0, PM_REMOVE) != 0) { 347 | TranslateMessage(&msg); 348 | DispatchMessageW(&msg); 349 | } 350 | } 351 | 352 | g_currently_processing_window: ^Window; 353 | 354 | proc win32_proc(hwnd: HWND, msg: u32, w: WPARAM, l: LPARAM) : LRESULT { 355 | if (msg == WM_ACTIVATEAPP) { 356 | assert(g_currently_processing_window != null); 357 | g_currently_processing_window.is_focused = w != 0; 358 | return 0; 359 | } 360 | else if (msg == WM_CLOSE) { 361 | assert(g_currently_processing_window != null); 362 | g_currently_processing_window.should_close = true; 363 | return 0; 364 | } 365 | else if (msg == WM_DESTROY) { 366 | PostQuitMessage(0); 367 | return 0; 368 | } 369 | else if (msg == WM_SIZE) { 370 | assert(g_currently_processing_window != null); 371 | 372 | width := LOWORDL(l); 373 | height := HIWORDL(l); 374 | 375 | if (width <= 0) width = 1; 376 | if (height <= 0) height = 1; 377 | 378 | g_currently_processing_window.width = cast(i64, width); 379 | g_currently_processing_window.height = cast(i64, height); 380 | g_currently_processing_window.aspect = cast(float, width) / cast(float, height); 381 | g_currently_processing_window.size = .{cast(float, g_currently_processing_window.width), cast(float, g_currently_processing_window.height)}; 382 | return 0; 383 | } 384 | 385 | return DefWindowProcW(hwnd, msg, w, l); 386 | } 387 | -------------------------------------------------------------------------------- /examples/directx_win32_triangle/types.hlsl: -------------------------------------------------------------------------------- 1 | struct VS_INPUT { 2 | float3 position : SV_POSITION; 3 | float3 texcoord : TEXCOORD; 4 | float4 color : COLOR; 5 | // float3 normal : NORMAL; 6 | // float3 tangent : TANGENT; 7 | // float3 bitangent : BITANGENT; 8 | }; 9 | 10 | struct PS_INPUT { 11 | float4 position : SV_POSITION; 12 | float3 texcoord : TEXCOORD; 13 | float4 color : COLOR; 14 | // float3 normal : NORMAL; 15 | // float3 tangent : TANGENT; 16 | // float3 bitangent : BITANGENT; 17 | // matrix tbn : TBN; 18 | // float3 world_position : WORLDPOS; 19 | }; 20 | 21 | struct PS_OUTPUT { 22 | float4 color : SV_Target0; 23 | // float4 bloom_color : SV_Target1; 24 | // float4 albedo : SV_Target2; 25 | // float4 position : SV_Target3; 26 | // float4 normal : SV_Target4; 27 | // float4 material : SV_Target5; 28 | }; 29 | 30 | // cbuffer CBUFFER_PASS : register(b0) { 31 | // float2 screen_dimensions; 32 | // matrix view_matrix; 33 | // matrix projection_matrix; 34 | // float3 camera_position; 35 | // }; 36 | 37 | // cbuffer CBUFFER_MODEL : register(b1) { 38 | // matrix model_matrix; 39 | // float4 model_color; 40 | // }; -------------------------------------------------------------------------------- /examples/entities/main.sif: -------------------------------------------------------------------------------- 1 | #include "core:basic.sif" 2 | #include "core:math.sif" 3 | #include "core:dynamic_array.sif" 4 | 5 | const EntityID := int; 6 | 7 | struct Entity { 8 | id: EntityID; 9 | position: Vector3; 10 | scale: Vector3; 11 | kind: typeid; 12 | } 13 | 14 | struct Player { 15 | using base: Entity; 16 | name: string; 17 | health: int; 18 | inventory: [4]EntityID; 19 | } 20 | 21 | struct Chest { 22 | using base: Entity; 23 | contents: [8]EntityID; 24 | } 25 | 26 | struct Weapon { 27 | using base: Entity; 28 | name: string; 29 | damage: int; 30 | } 31 | 32 | struct Helmet { 33 | using base: Entity; 34 | defense: int; 35 | } 36 | 37 | proc player_loot_chest(player: ^Player, chest: ^Chest) { 38 | for (chest_idx := 0; chest_idx < chest.contents.count; chest_idx += 1) { 39 | item := chest.contents[chest_idx]; 40 | if (item == 0) { 41 | // skip empty chest slots 42 | continue; 43 | } 44 | // find an empty slot in the players inventory 45 | found_inventory_slot := -1; 46 | for (inv_idx := 0; inv_idx < player.inventory.count; inv_idx += 1) { 47 | if (player.inventory[inv_idx] == 0) { 48 | found_inventory_slot = inv_idx; 49 | break; 50 | } 51 | } 52 | if (found_inventory_slot != -1) { 53 | // transfer to players inventory 54 | player.inventory[found_inventory_slot] = chest.contents[chest_idx]; 55 | chest.contents[chest_idx] = 0; 56 | } 57 | } 58 | } 59 | 60 | proc put_items_in_chest(chest: ^Chest, items: []^Entity) : int { 61 | item_index := 0; 62 | for (chest_idx := 0; chest_idx < chest.contents.count && item_index < items.count; chest_idx += 1) { 63 | if (chest.contents[chest_idx] != 0) { 64 | // skip slots with stuff in them already 65 | continue; 66 | } 67 | chest.contents[chest_idx] = items[item_index].id; 68 | item_index += 1; 69 | } 70 | // return the number of items we were able to successfully put in the chest 71 | return item_index; 72 | } 73 | 74 | g_last_entity_id: EntityID; 75 | proc make_entity(entities: ^Dynamic_Array!(^Entity), $T: typeid) : ^T { 76 | // increment first so there will be no entity id '0'. this allows us to use '0' to mean 'null' for entity IDs 77 | g_last_entity_id += 1; 78 | 79 | ptr := new(T, default_allocator()); // you shouldn't allocate entities individially in a real game of course but for this example it's fine 80 | ptr.id = g_last_entity_id; 81 | ptr.kind = T; 82 | ptr.scale = .{1, 1, 1}; 83 | append(entities, &ptr.base); 84 | return ptr; 85 | } 86 | 87 | proc main() { 88 | entities: Dynamic_Array!(^Entity); // a dynamic array of pointers is not a good way to store entities, but again, fine for this example 89 | entities.allocator = default_allocator(); 90 | 91 | // make a player 92 | player := make_entity(&entities, Player); 93 | player.position = .{5, 0, 5}; // we are able to access position through Player because of the 'using' in the Player struct 94 | player.name = "John"; 95 | player.health = 10; 96 | 97 | // make a chest 98 | chest := make_entity(&entities, Chest); 99 | chest.position = .{6, 0, 5}; // right next to the player 100 | 101 | // make some items 102 | sword := make_entity(&entities, Weapon); 103 | sword.name = "Sword"; 104 | sword.damage = 3; 105 | bow := make_entity(&entities, Weapon); 106 | bow.name = "Bow"; 107 | bow.damage = 2; 108 | helmet := make_entity(&entities, Helmet); 109 | helmet.defense = 1; 110 | 111 | // print all the entities 112 | print("All entities:\n"); 113 | for (i := 0; i < entities.count; i += 1) { 114 | entity := entities[i]; 115 | if (entity.kind == Player) { 116 | printa(cast(^Player, entity)^); 117 | } 118 | else if (entity.kind == Chest) { 119 | printa(cast(^Chest, entity)^); 120 | } 121 | else if (entity.kind == Weapon) { 122 | printa(cast(^Weapon, entity)^); 123 | } 124 | else if (entity.kind == Helmet) { 125 | printa(cast(^Helmet, entity)^); 126 | } 127 | } 128 | 129 | // put the items in the chest 130 | array_of_items := [3]^Entity.{ 131 | &sword.base, 132 | &bow.base, 133 | &helmet.base 134 | }; 135 | put_items_in_chest(chest, to_slice(&array_of_items)); 136 | 137 | // print the chest to see the items added to it 138 | print("\nChest with contents:\n"); 139 | printa(chest^); 140 | 141 | // loot the chest 142 | player_loot_chest(player, chest); 143 | 144 | // print the player and the chest 145 | print("\nAfter player looted chest:\n"); 146 | printa(chest^); 147 | printa(player^); 148 | } -------------------------------------------------------------------------------- /examples/minidemo/main.sif: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | #include "core:basic.sif" 8 | #include "core:math.sif" 9 | #include "core:json.sif" 10 | 11 | proc main() { 12 | print("---- VARIABLES ----\n"); 13 | variable1: int = 123; 14 | variable2: = 456; 15 | variable3 := 789; 16 | printa(variable1, variable2, variable3); 17 | 18 | my_string := "Hello, World!"; 19 | printa(my_string); 20 | 21 | hello: string; 22 | hello.data = my_string.data; 23 | hello.count = 5; 24 | printa(hello); 25 | 26 | print("\n\n\n"); 27 | 28 | 29 | 30 | print("---- STRUCTS ----\n"); 31 | struct Person { 32 | name: string; 33 | age: int; 34 | } 35 | 36 | bob: Person; 37 | bob.name = "Bob"; 38 | bob.age = 41; 39 | printa(bob); 40 | 41 | alice := Person.{"Alice", 32}; 42 | printa(alice); 43 | 44 | alice = .{"Alice", 40}; 45 | printa(alice); 46 | 47 | alice = .{}; 48 | printa(alice); 49 | 50 | print("\n\n\n"); 51 | 52 | 53 | 54 | print("---- TYPE INFO ----\n"); 55 | struct My_Struct { 56 | a: int; 57 | b: bool; 58 | c: string; 59 | d: ^My_Struct; 60 | } 61 | 62 | type_info : ^Type_Info = get_type_info(My_Struct); 63 | assert(type_info.kind == Type_Info_Struct); 64 | struct_info := cast(^Type_Info_Struct, type_info); 65 | for (i := 0; i < struct_info.fields.count; i += 1) { 66 | field := struct_info.fields[i]; 67 | printa(field); 68 | printa(field.type^); 69 | print("\n"); 70 | } 71 | 72 | my_struct := My_Struct.{12, true, "this is a string", null}; 73 | printa(json_serialize(&my_struct, default_allocator())); 74 | 75 | 76 | print("\n\n\n"); 77 | 78 | 79 | 80 | print("---- using ----\n"); 81 | struct Game_Object { 82 | using position: Vector3; 83 | name: string; 84 | } 85 | 86 | go: Game_Object; 87 | go.position = .{1, 4, 9}; 88 | go.name = "New Game Object"; 89 | printa(go); 90 | 91 | go.x = 12; 92 | go.y = 34; 93 | go.z = 56; 94 | printa(go); 95 | 96 | struct Sword { 97 | using base: Game_Object; 98 | using stats: Equipment_Stats; 99 | } 100 | 101 | struct Equipment_Stats { 102 | damage: int; 103 | defense: int; 104 | luck_bonus: int; 105 | } 106 | 107 | sword: Sword; 108 | sword.name = "Excalibur"; 109 | sword.position = .{45, 0, 124.2}; 110 | sword.damage = 9999; 111 | sword.defense = 0; 112 | sword.luck_bonus = 5; 113 | printa(sword); 114 | 115 | print("\n"); 116 | 117 | using sword; 118 | print("name: %\n", name); 119 | print("position: %\n", position); 120 | print("damage: %\n", damage); 121 | print("defense: %\n", defense); 122 | print("luck_bonus: %\n", luck_bonus); 123 | 124 | print("\n\n\n"); 125 | 126 | 127 | 128 | print("---- PROCEDURAL POLYMORPHISM WITH TYPES ----\n"); 129 | proc add_things(a: $T, b: T) : T { 130 | return a + b; 131 | } 132 | 133 | int1 := 12; 134 | int2 := 23; 135 | printa(add_things(int1, int2)); 136 | 137 | float1: float = 34; 138 | float2: float = 45; 139 | printa(add_things(float1, float2)); 140 | 141 | vector1 := Vector3.{1, 2, 3}; 142 | vector2 := Vector3.{4, 5, 6}; 143 | printa(add_things(vector1, vector2)); 144 | 145 | // string1 := "Hello, "; 146 | // string2 := "World!"; 147 | // printa(add_things(string1, string2)); 148 | 149 | print("\n\n\n"); 150 | 151 | 152 | 153 | print("---- PROCEDURAL POLYMORPHISM WITH VALUES ----\n"); 154 | proc add_constants($a: $T, $b: T) : T { 155 | return a + b; 156 | } 157 | 158 | printa(add_constants(1, 2)); 159 | const THREE := 3; 160 | const FOUR := 4; 161 | printa(add_constants(THREE, FOUR)); 162 | printa(add_constants("Hello, ", "World!")); 163 | 164 | print("\n"); 165 | 166 | proc print_fields_of_type($T: typeid) { 167 | print("in print_fields_of_type with T == %\n", T); 168 | type_info := get_type_info(T); 169 | assert(type_info.kind == Type_Info_Struct); 170 | struct_info := cast(^Type_Info_Struct, type_info); 171 | for (i := 0; i < struct_info.fields.count; i += 1) { 172 | field := struct_info.fields[i]; 173 | printa(field); 174 | } 175 | print("\n"); 176 | } 177 | 178 | print_fields_of_type(Vector3); 179 | print_fields_of_type(Game_Object); 180 | print_fields_of_type(Sword); 181 | 182 | print("\n\n"); 183 | 184 | 185 | 186 | print("---- STRUCTURAL POLYMORPHISM ----\n"); 187 | struct Container!($N: int, $T: typeid) { 188 | data: [N]T; 189 | } 190 | 191 | int_container: Container!(1, int); 192 | int_container.data[0] = 123; 193 | printa(int_container); 194 | 195 | float_container: Container!(2, float); 196 | float_container.data = .{34, 321}; 197 | printa(float_container); 198 | 199 | string_container: Container!(4, string); 200 | string_container.data[0] = "hello"; 201 | string_container.data[2] = "there"; 202 | printa(string_container); 203 | } 204 | -------------------------------------------------------------------------------- /examples/tests/defer_test.sif: -------------------------------------------------------------------------------- 1 | proc test_defer() { 2 | a := 123; 3 | 4 | // 5 | { 6 | defer assert(a == 321); 7 | defer a = 321; 8 | defer assert(a == 123); 9 | assert(a == 123); 10 | } 11 | assert(a == 321); 12 | 13 | // 14 | for (i := 0; i < 10; i += 1) { 15 | if (i > 5) { 16 | defer a = 149; 17 | break; 18 | } 19 | } 20 | assert(a == 149); 21 | 22 | // 23 | for (i := 0; i < 10; i += 1) { 24 | if (i > 15) { 25 | defer a = 941; 26 | break; 27 | } 28 | } 29 | assert(a == 149); 30 | 31 | // 32 | for (i := 0; i < 10; i += 1) { 33 | if (i > 5) { 34 | defer a = 378; 35 | continue; 36 | } 37 | } 38 | assert(a == 378); 39 | 40 | // 41 | for (i := 0; i < 10; i += 1) { 42 | if (i > 15) { 43 | defer a = 941; 44 | continue; 45 | } 46 | } 47 | assert(a == 378); 48 | 49 | assert(defer_some_global == 0); 50 | val := proc_test(); 51 | assert(defer_some_global == 531); 52 | assert(val == 123); 53 | } 54 | 55 | defer_some_global: int; 56 | 57 | proc proc_test() : int { 58 | defer_some_global = 123; 59 | defer defer_some_global = 531; 60 | a := 123; 61 | defer a = 321; // defers are executed _after_ return values are evaluated, so the 'return a;' below will return 123 62 | return a; 63 | } -------------------------------------------------------------------------------- /examples/tests/dynamic_array_test.sif: -------------------------------------------------------------------------------- 1 | #include "core:dynamic_array.sif" 2 | 3 | proc test_dynamic_array() { 4 | dyn: Dynamic_Array!(int); 5 | dyn.allocator = default_allocator(); 6 | 7 | _do_all_the_dynamic_array_things(&dyn); 8 | 9 | // clear it and do it all again! 10 | clear_dynamic_array(&dyn); 11 | assert(dyn.count == 0); 12 | assert(dyn.elements.count == 0); 13 | assert(dyn.capacity == 20); 14 | dyn.capacity = 0; 15 | _do_all_the_dynamic_array_things(&dyn); 16 | 17 | destroy_dynamic_array(dyn); 18 | } 19 | 20 | proc _do_all_the_dynamic_array_things(dyn: ^Dynamic_Array!($T)) { 21 | // 22 | assert(dyn.count == 0); 23 | assert(dyn.elements.count == 0); 24 | assert(dyn.capacity == 0); 25 | 26 | // 27 | append(dyn, 1); 28 | assert(dyn.count == 1); 29 | assert(dyn.elements.count == 1); 30 | assert(dyn.capacity == 8); 31 | 32 | // 33 | append(dyn, 2); 34 | append(dyn, 3); 35 | assert(dyn.count == 3); 36 | assert(dyn.elements.count == 3); 37 | 38 | // 39 | val := pop(dyn); 40 | assert(dyn.count == 2); 41 | assert(dyn.elements.count == 2); 42 | assert(val == 3); 43 | 44 | // 45 | append(dyn, 3); 46 | append(dyn, 4); 47 | append(dyn, 5); 48 | assert(dyn^[0] == 1); 49 | assert(dyn^[1] == 2); 50 | assert(dyn^[2] == 3); 51 | assert(dyn^[3] == 4); 52 | assert(dyn^[4] == 5); 53 | 54 | // 55 | assert(dyn.count == 5); 56 | assert(dyn.elements.count == 5); 57 | assert(dyn.capacity == 8); 58 | append(dyn, 6); 59 | append(dyn, 7); 60 | append(dyn, 8); 61 | assert(dyn.count == 8); 62 | assert(dyn.elements.count == 8); 63 | assert(dyn.capacity == 8); 64 | append(dyn, 9); 65 | assert(dyn.count == 9); 66 | assert(dyn.elements.count == 9); 67 | assert(dyn.capacity == 24); 68 | 69 | // 70 | pop(dyn); 71 | pop(dyn); 72 | pop(dyn); 73 | pop(dyn); 74 | assert(dyn.count == 5); 75 | assert(dyn.elements.count == 5); 76 | assert(dyn^[0] == 1); 77 | assert(dyn^[1] == 2); 78 | assert(dyn^[2] == 3); 79 | assert(dyn^[3] == 4); 80 | assert(dyn^[4] == 5); 81 | unordered_remove(dyn, 2); 82 | assert(dyn.count == 4); 83 | assert(dyn.elements.count == 4); 84 | assert(dyn^[0] == 1); 85 | assert(dyn^[1] == 2); 86 | assert(dyn^[2] == 5); 87 | assert(dyn^[3] == 4); 88 | ordered_remove(dyn, 1); 89 | assert(dyn.count == 3); 90 | assert(dyn.elements.count == 3); 91 | assert(dyn^[0] == 1); 92 | assert(dyn^[1] == 5); 93 | assert(dyn^[2] == 4); 94 | 95 | // 96 | insert(dyn, 1, 10); 97 | assert(dyn.count == 4); 98 | assert(dyn.elements.count == 4); 99 | assert(dyn^[0] == 1); 100 | assert(dyn^[1] == 10); 101 | assert(dyn^[2] == 5); 102 | assert(dyn^[3] == 4); 103 | 104 | // 105 | dyn.capacity = 6; 106 | insert(dyn, 3, 23); 107 | assert(dyn.count == 5); 108 | assert(dyn.elements.count == 5); 109 | assert(dyn.capacity == 6); 110 | insert(dyn, 3, 23); 111 | assert(dyn.count == 6); 112 | assert(dyn.elements.count == 6); 113 | assert(dyn.capacity == 6); 114 | insert(dyn, 3, 23); 115 | assert(dyn.count == 7); 116 | assert(dyn.elements.count == 7); 117 | assert(dyn.capacity == 20); 118 | } -------------------------------------------------------------------------------- /examples/tests/file_io_test.sif: -------------------------------------------------------------------------------- 1 | proc test_file_io() { 2 | data1 := read_entire_file("examples/tests/doesnt_exist.txt", default_allocator()); 3 | assert(data1.data == null); 4 | assert(data1.count == 0); 5 | 6 | data2 := read_entire_file("examples/tests/test_file.txt", default_allocator()); 7 | assert(transmute(string, data2) == "this is a cool file"); 8 | 9 | write_entire_file("examples/tests/doesnt_exist.txt", transmute([]byte, "some text to create a file with"), default_allocator()); 10 | 11 | data3 := read_entire_file("examples/tests/doesnt_exist.txt", default_allocator()); 12 | assert(transmute(string, data3) == "some text to create a file with"); 13 | 14 | delete_file("examples/tests/doesnt_exist.txt", default_allocator()); 15 | 16 | data4 := read_entire_file("examples/tests/doesnt_exist.txt", default_allocator()); 17 | assert(data4.data == null); 18 | assert(data4.count == 0); 19 | } 20 | 21 | #include "core:os.sif" -------------------------------------------------------------------------------- /examples/tests/json_test.sif: -------------------------------------------------------------------------------- 1 | #include "core:json.sif" 2 | 3 | // todo(josh): add more things to the JSON like arrays and bools and slices and pointers 4 | 5 | proc test_json() { 6 | struct My_Struct { 7 | foo: int; 8 | nested_struct: Other_Struct; 9 | } 10 | 11 | struct Other_Struct { 12 | a_string: string; 13 | a_float: float; 14 | } 15 | 16 | json := "{ \"foo\": 123, \"nested_struct\": { \"a_string\": \"wow this is some json\", \"a_float\": 2.52 } }"; 17 | 18 | json_value := json_parse_value(json, default_allocator()); 19 | 20 | my_struct: My_Struct; 21 | json_write_value(&my_struct, json_value, default_allocator()); 22 | 23 | assert(my_struct.foo == 123); 24 | assert(my_struct.nested_struct.a_string == "wow this is some json"); 25 | assert(my_struct.nested_struct.a_float == 2.52); 26 | } -------------------------------------------------------------------------------- /examples/tests/local_declarations.sif: -------------------------------------------------------------------------------- 1 | proc test_local_declarations() { 2 | assert(qwe1() == 123); 3 | assert(qwe2() == 321); 4 | assert(Baz.Qux.qwe3() == 456); 5 | Foo.Qux.bar(); 6 | Baz.Qux.bar(); 7 | assert(Foo.Qux != Baz.Qux); 8 | assert(Foo.Qux.bar != Baz.Qux.bar); 9 | } 10 | 11 | struct Foo { 12 | struct Qux { 13 | proc bar() { } 14 | } 15 | enum Foo_Enum { 16 | A; B; C; 17 | } 18 | } 19 | 20 | struct Baz { 21 | struct Qux { 22 | proc bar() { } 23 | proc qwe3() : int { 24 | enum Foo_Enum { 25 | A; B; C; 26 | } 27 | 28 | assert(Foo_Enum != Foo.Foo_Enum); 29 | 30 | struct Zxc { 31 | a: int; 32 | } 33 | 34 | proc asd() : int { 35 | z: Zxc; 36 | z.a = 456; 37 | return z.a; 38 | } 39 | 40 | return asd(); 41 | } 42 | } 43 | } 44 | 45 | proc qwe1() : int { 46 | struct Zxc { 47 | a: int; 48 | } 49 | 50 | proc asd() : int { 51 | z: Zxc; 52 | z.a = 123; 53 | return z.a; 54 | } 55 | 56 | return asd(); 57 | } 58 | 59 | proc qwe2() : int { 60 | struct Zxc { 61 | a: int; 62 | } 63 | 64 | proc asd() : int { 65 | z: Zxc; 66 | z.a = 321; 67 | return z.a; 68 | } 69 | 70 | return asd(); 71 | } -------------------------------------------------------------------------------- /examples/tests/main.sif: -------------------------------------------------------------------------------- 1 | proc main() { 2 | run_test(test_numbers, "numbers"); 3 | run_test(test_defer, "defer"); 4 | run_test(test_local_declarations, "local_declarations"); 5 | run_test(test_json, "json"); 6 | run_test(test_strings, "strings"); 7 | run_test(test_dynamic_array, "dynamic_array"); 8 | run_test(test_file_io, "file_io"); 9 | run_test(test_miscellanea, "miscellanea"); 10 | 11 | print("Done tests.\n"); 12 | } 13 | 14 | proc run_test(procedure: proc(), test_name: string) { 15 | const NUM_DOTS := 30; 16 | 17 | print("% ", test_name); 18 | dots_to_print := max(NUM_DOTS - test_name.count, 0) + 5; 19 | for (i := 0; i < dots_to_print; i += 1) { 20 | print("."); 21 | } 22 | procedure(); 23 | print(" DONE\n"); 24 | } 25 | 26 | #include "numbers_test.sif" 27 | #include "miscellaneous_tests.sif" 28 | #include "defer_test.sif" 29 | #include "local_declarations.sif" 30 | #include "json_test.sif" 31 | #include "strings_test.sif" 32 | #include "dynamic_array_test.sif" 33 | #include "file_io_test.sif" 34 | 35 | #include "core:basic.sif" 36 | -------------------------------------------------------------------------------- /examples/tests/miscellaneous_tests.sif: -------------------------------------------------------------------------------- 1 | proc test_miscellanea() { 2 | { 3 | a := 2; 4 | b := 3; 5 | a, b = b, a; 6 | assert(a == 3); 7 | assert(b == 2); 8 | } 9 | 10 | { 11 | proc select(things: $T, bools: []bool, allocator: Allocator) : T { 12 | results := new_slice(typeofelement(T), bools.count, allocator); 13 | result_index := 0; 14 | for (i := 0; i < bools.count; i += 1) { 15 | if (bools[i]) { 16 | results[result_index] = things[i]; 17 | result_index += 1; 18 | } 19 | } 20 | results.count = result_index; 21 | return transmute(T, results); 22 | } 23 | 24 | { 25 | things := [3]int.{1, 4, 9}; 26 | bools := [3]bool.{true, false, true}; 27 | selected := select(to_slice(&things), to_slice(&bools), default_allocator()); 28 | assert(selected.count == 2); 29 | assert(selected[0] == 1); 30 | assert(selected[1] == 9); 31 | } 32 | 33 | { 34 | bools := [5]bool.{false, true, true, true, false}; 35 | chars := select("Hello", to_slice(&bools), default_allocator()); 36 | assert(chars.count == 3); 37 | assert(chars[0] == 'e'); 38 | assert(chars[1] == 'l'); 39 | assert(chars[2] == 'l'); 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /examples/tests/numbers_test.sif: -------------------------------------------------------------------------------- 1 | #include "core:random.sif" 2 | 3 | proc test_numbers() { 4 | // untyped and typed things 5 | { 6 | a := 10; 7 | assert(a == 10); 8 | assert(typeof(a) == int); 9 | assert(a / 2 == 5); 10 | assert(typeof(a / 2) == int); 11 | 12 | b := a / 2; 13 | assert(typeof(b) == int); 14 | 15 | c := 2 / a; 16 | assert(typeof(c) == int); 17 | 18 | d : u32 = 10; 19 | assert(d == 10); 20 | assert(typeof(d) == u32); 21 | assert(d / 2 == 5); 22 | assert(typeof(d / 2) == u32); 23 | assert(typeof(2 / d) == u32); 24 | 25 | e := 10.0; 26 | assert(typeof(e) == float); 27 | assert(typeof(e) == f32); 28 | assert(typeof(e / 2) == float); 29 | assert(typeof(2 / e) == float); 30 | 31 | assert(typeof(cast(float, 2) / 2) == float); 32 | assert(typeof(cast(u32, 2) / 2) == u32); 33 | assert(typeof(2 / cast(u8, 2)) == u8); 34 | 35 | f : u16 = 16 / 2; 36 | assert(f == 8); 37 | 38 | g : float = 16 / 2.0; 39 | assert(f == 8); 40 | 41 | h : float = 16.0 / 2; 42 | assert(h == 8); 43 | } 44 | 45 | // random 46 | { 47 | r: Random_State; 48 | init_random_state(&r, 61274628); 49 | num0: int; 50 | num1: int; 51 | num2: int; 52 | num3: int; 53 | num4: int; 54 | num5: int; 55 | num6: int; 56 | num7: int; 57 | num8: int; 58 | num9: int; 59 | for (i := 0; i < 10000; i += 1) { 60 | val := random_f32(&r); 61 | if (val < 0.1) { num0 += 1; } 62 | else if (val < 0.2) { num1 += 1; } 63 | else if (val < 0.3) { num2 += 1; } 64 | else if (val < 0.4) { num3 += 1; } 65 | else if (val < 0.5) { num4 += 1; } 66 | else if (val < 0.6) { num5 += 1; } 67 | else if (val < 0.7) { num6 += 1; } 68 | else if (val < 0.8) { num7 += 1; } 69 | else if (val < 0.9) { num8 += 1; } 70 | else if (val < 1.0) { num9 += 1; } 71 | } 72 | assert(num0 == 1007); 73 | assert(num1 == 1013); 74 | assert(num2 == 961); 75 | assert(num3 == 1004); 76 | assert(num4 == 953); 77 | assert(num5 == 1042); 78 | assert(num6 == 1054); 79 | assert(num7 == 1012); 80 | assert(num8 == 1009); 81 | assert(num9 == 945); 82 | } 83 | 84 | // number constants 85 | { 86 | f32_min : f32 = 1.175494351e-38; 87 | f32_max : f32 = 3.402823466e+38; 88 | f64_min : f64 = 2.2250738585072014e-308; 89 | f64_max : f64 = 1.7976931348623158e+308; 90 | tau := 6.28318530717958647692528676655900576; 91 | pi := 3.14159265358979323846264338327950288; 92 | e := 2.71828182845904523536; 93 | 94 | assert(f32_min != 0); 95 | assert(f32_max != 0); 96 | assert(f64_min != 0); 97 | assert(f64_max != 0); 98 | assert(tau != 0); 99 | assert(pi != 0); 100 | assert(e != 0); 101 | 102 | assert(f32_min == 1.175494351e-38); 103 | assert(f32_max == 3.402823466e+38); 104 | assert(f64_min == 2.2250738585072014e-308); 105 | assert(f64_max == 1.7976931348623158e+308); 106 | assert(tau == 6.28318530717958647692528676655900576); 107 | assert(pi == 3.14159265358979323846264338327950288); 108 | assert(e == 2.71828182845904523536); 109 | 110 | hexi: int = 0xff; 111 | assert(hexi == 255); 112 | hexu: uint = 0xff; 113 | assert(hexu == 255); 114 | hexf: float = 0xff; 115 | assert(hexf == 255); 116 | } 117 | } -------------------------------------------------------------------------------- /examples/tests/strings_test.sif: -------------------------------------------------------------------------------- 1 | #include "core:strings.sif" 2 | #include "core:math.sif" 3 | 4 | proc test_strings() { 5 | str := "Hello, World!"; 6 | pieces := string_split(str, ',', default_allocator()); 7 | assert(pieces[0] == "Hello"); 8 | assert(pieces[1] == " World!"); 9 | 10 | assert(aprint(default_allocator(), "this % is a % string %", 123, [2]int.{4, 2}, Vector3.{1, 4, 9}) == "this 123 is a [4, 2] string Vector3.{x = 1.000000, y = 4.000000, z = 9.000000}"); 11 | 12 | index: int; 13 | assert(index_of("foozlez", 'z', &index)); 14 | assert(index == 3); 15 | assert(!index_of("foozlez", 'Z', &index)); 16 | 17 | assert(last_index_of("foozlez", 'z', &index)); 18 | assert(index == 6); 19 | assert(!last_index_of("foozlez", 'Z', &index)); 20 | 21 | assert(string_ends_with("foozle", "zle")); 22 | assert(!string_ends_with("foozle", "foo")); 23 | assert(string_starts_with("foozle", "foo")); 24 | assert(!string_starts_with("foozle", "zle")); 25 | assert(!string_starts_with("foozle", "")); 26 | assert(!string_ends_with("foozle", "")); 27 | 28 | { 29 | strings := [4]string.{ 30 | "foo", "bar", "baz", "qux" 31 | }; 32 | full_string := concatenate(to_slice(&strings), default_allocator()); 33 | assert(full_string == "foobarbazqux"); 34 | } 35 | 36 | { 37 | strings := [4]string.{ 38 | "", "", "", "" 39 | }; 40 | full_string := concatenate(to_slice(&strings), default_allocator()); 41 | assert(full_string == ""); 42 | assert(full_string.data == null); 43 | } 44 | 45 | { 46 | strings := [4]string.{ 47 | "foo", "", "bar", "" 48 | }; 49 | full_string := concatenate(to_slice(&strings), default_allocator()); 50 | assert(full_string == "foobar"); 51 | } 52 | 53 | { 54 | a := "a really really really really really really really really really really really long string"; 55 | assert(a.count == 90); 56 | cstr := clone_to_cstring(a, default_allocator()); 57 | assert(strlen(cstr) == 90); 58 | str := clone_to_string(cstr, default_allocator()); 59 | assert(str.count == 90); 60 | str2 := cstring_to_string(cstr); 61 | assert(str2.count == 90); 62 | cstr2 := unsafe_string_to_cstring(a); 63 | assert(cast(^byte, cstr2) == a.data); 64 | } 65 | } -------------------------------------------------------------------------------- /examples/tests/test_file.txt: -------------------------------------------------------------------------------- 1 | this is a cool file -------------------------------------------------------------------------------- /src/basic.cpp: -------------------------------------------------------------------------------- 1 | #include "basic.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | 10 | bool is_power_of_two(uintptr_t n) { 11 | return n > 0 && (n & (n-1)) == 0; 12 | } 13 | 14 | uintptr_t align_forward(uintptr_t p, uintptr_t align) { 15 | assert(is_power_of_two(align)); 16 | p = (p + (align - 1)) & (~(align - 1)); 17 | return p; 18 | } 19 | 20 | uintptr_t align_backward(uintptr_t p, uintptr_t align) { 21 | return align_forward(p - align + 1, align); 22 | } 23 | 24 | void zero_memory(void *memory_void, int length) { 25 | char *memory = (char *)memory_void; 26 | uintptr_t start = (uintptr_t)memory; 27 | uintptr_t start_aligned = align_forward(start, alignof(uintptr_t)); 28 | uintptr_t end = start + (uintptr_t)length; 29 | uintptr_t end_aligned = align_backward(end, alignof(uintptr_t)); 30 | 31 | for (uintptr_t i = start; i < start_aligned; i++) { 32 | memory[i-start] = 0; 33 | } 34 | 35 | for (uintptr_t i = start_aligned; i < end_aligned; i += sizeof(uintptr_t)) { 36 | *((uintptr_t *)&memory[i-start]) = 0; 37 | } 38 | 39 | for (uintptr_t i = end_aligned; i < end; i++) { 40 | memory[i-start] = 0; 41 | } 42 | } 43 | 44 | 45 | 46 | float min(float a, float b) { 47 | if (a < b) return a; 48 | return b; 49 | } 50 | 51 | float max(float a, float b) { 52 | if (a > b) return a; 53 | return b; 54 | } 55 | 56 | int min(int a, int b) { 57 | if (a < b) return a; 58 | return b; 59 | } 60 | 61 | int max(int a, int b) { 62 | if (a > b) return a; 63 | return b; 64 | } 65 | 66 | 67 | 68 | int modulo(int a, int b) { 69 | int r = a % b; 70 | return r < 0 ? r + b : r; 71 | } 72 | 73 | 74 | 75 | void *alloc(Allocator allocator, int size, int alignment, bool zero) { 76 | assert(allocator.alloc_proc != nullptr && "Alloc proc was nullptr for allocator"); 77 | void *ptr = allocator.alloc_proc(allocator.data, size, alignment); 78 | if (zero) { 79 | memset(ptr, 0, size); 80 | } 81 | return ptr; 82 | } 83 | 84 | void free(Allocator allocator, void *ptr) { 85 | assert(allocator.free_proc != nullptr && "Free proc was nullptr for allocator"); 86 | allocator.free_proc(allocator.data, ptr); 87 | } 88 | 89 | 90 | 91 | void *default_allocator_alloc(void *allocator, int size, int alignment) { 92 | return malloc(size); 93 | } 94 | 95 | void default_allocator_free(void *allocator, void *ptr) { 96 | free(ptr); 97 | } 98 | 99 | Allocator default_allocator() { 100 | Allocator a = {}; 101 | a.alloc_proc = default_allocator_alloc; 102 | a.free_proc = default_allocator_free; 103 | return a; 104 | } 105 | 106 | 107 | 108 | byte *buffer_allocate(byte *buffer, int buffer_len, int *offset, int size, int alignment, bool panic_on_oom) { 109 | // Don't allow allocations of zero size. This would likely return a 110 | // pointer to a different allocation, causing many problems. 111 | if (size == 0) { 112 | return nullptr; 113 | } 114 | 115 | // todo(josh): The `align_forward()` call and the `start + size` below 116 | // that could overflow if the `size` or `align` parameters are super huge 117 | 118 | int start = align_forward(*offset, alignment); 119 | 120 | // Don't allow allocations that would extend past the end of the buffer. 121 | if ((start + size) > buffer_len) { 122 | if (panic_on_oom) { 123 | assert(0 && "buffer_allocate ran out of memory"); 124 | } 125 | return nullptr; 126 | } 127 | 128 | *offset = start + size; 129 | byte *ptr = &buffer[start]; 130 | // zero_memory(ptr, size); 131 | return ptr; 132 | } 133 | 134 | 135 | 136 | void *null_allocator_alloc(void *allocator, int size, int align) { 137 | assert(false && "Tried to allocate with the null allocator"); 138 | return nullptr; 139 | } 140 | 141 | void null_allocator_free(void *allocator, void *ptr) { 142 | } 143 | 144 | Allocator null_allocator() { 145 | Allocator a = {}; 146 | return a; 147 | } 148 | 149 | 150 | 151 | void init_arena(Arena *arena, void *backing, int backing_size, bool panic_on_oom) { 152 | arena->memory = (byte *)backing; 153 | arena->memory_size = backing_size; 154 | arena->cur_offset = 0; 155 | arena->panic_on_oom = panic_on_oom; 156 | } 157 | 158 | void *arena_alloc(void *allocator, int size, int align) { 159 | Arena *arena = (Arena *)allocator; 160 | return buffer_allocate(arena->memory, arena->memory_size, &arena->cur_offset, size, align, arena->panic_on_oom); 161 | } 162 | 163 | void arena_free(void *allocator, void *ptr) { 164 | // note(josh): freeing from arenas does nothing. 165 | } 166 | 167 | void arena_clear(Arena *arena) { 168 | arena->cur_offset = 0; 169 | } 170 | 171 | Allocator arena_allocator(Arena *arena) { 172 | Allocator a = {}; 173 | a.data = arena; 174 | a.alloc_proc = arena_alloc; 175 | a.free_proc = arena_free; 176 | return a; 177 | } 178 | 179 | 180 | 181 | void init_dynamic_arena(Dynamic_Arena *dyn, int starting_chunk_size, Allocator backing_allocator) { 182 | dyn->chunk_size = starting_chunk_size; 183 | dyn->backing_allocator = backing_allocator; 184 | dyn->arenas = make_array(backing_allocator); 185 | dyn->leaked_allocations = make_array(backing_allocator); 186 | Arena *arena = dyn->arenas.append(); 187 | init_arena(arena, alloc(backing_allocator, starting_chunk_size, DEFAULT_ALIGNMENT, false), starting_chunk_size, false); 188 | } 189 | 190 | void *dynamic_arena_alloc(void *allocator, int size, int align) { 191 | Dynamic_Arena *dyn = (Dynamic_Arena *)allocator; 192 | dyn->spinlock.lock(); 193 | defer(dyn->spinlock.unlock()); 194 | Arena *current_arena = &dyn->arenas[dyn->current_arena_index]; 195 | void *ptr = arena_alloc(current_arena, size, align); 196 | if (!ptr) { 197 | // we've reached the end of the arena 198 | if (dyn->current_arena_index+1 >= dyn->arenas.count) { 199 | // make a new arena 200 | current_arena = dyn->arenas.append(); 201 | dyn->chunk_size *= 2; 202 | init_arena(current_arena, alloc(dyn->backing_allocator, dyn->chunk_size, DEFAULT_ALIGNMENT, false), dyn->chunk_size, false); 203 | } 204 | else { 205 | // use the next one in the list 206 | current_arena = &dyn->arenas[dyn->current_arena_index+1]; 207 | } 208 | dyn->current_arena_index += 1; 209 | 210 | // retry the allocation 211 | ptr = arena_alloc(current_arena, size, align); 212 | if (!ptr) { 213 | // if it STILL failed then that means that the allocation is too big for our chunk size. fallback to the backing allocator 214 | ptr = alloc(dyn->backing_allocator, size, align); 215 | assert(ptr != nullptr); 216 | dyn->leaked_allocations.append(ptr); 217 | } 218 | } 219 | assert(ptr != nullptr); 220 | return ptr; 221 | } 222 | 223 | void dynamic_arena_free(void *allocator, void *ptr) { 224 | // note(josh): freeing from arenas does nothing 225 | } 226 | 227 | void dynamic_arena_clear(Dynamic_Arena *dyn) { 228 | dyn->spinlock.lock(); 229 | defer(dyn->spinlock.unlock()); 230 | For (idx, dyn->arenas) { 231 | arena_clear(&dyn->arenas[idx]); 232 | } 233 | dyn->current_arena_index = 0; 234 | } 235 | 236 | Allocator dynamic_arena_allocator(Dynamic_Arena *dyn) { 237 | Allocator a = {}; 238 | a.data = dyn; 239 | a.alloc_proc = dynamic_arena_alloc; 240 | a.free_proc = dynamic_arena_free; 241 | return a; 242 | } 243 | 244 | void destroy_dynamic_arena(Dynamic_Arena *dyn) { 245 | dyn->spinlock.lock(); 246 | defer(dyn->spinlock.unlock()); 247 | For (idx, dyn->arenas) { 248 | free(dyn->backing_allocator, dyn->arenas[idx].memory); 249 | } 250 | dyn->arenas.destroy(); 251 | For (idx, dyn->leaked_allocations) { 252 | free(dyn->backing_allocator, dyn->leaked_allocations[idx]); 253 | } 254 | dyn->leaked_allocations.destroy(); 255 | } 256 | 257 | 258 | 259 | void init_pool_allocator(Pool_Allocator *pool, Allocator backing_allocator, int slot_size, int num_slots) { 260 | assert(slot_size > 0); 261 | assert(num_slots > 0); 262 | pool->backing_allocator = backing_allocator; 263 | pool->memory_size = slot_size * num_slots; 264 | pool->slot_size = slot_size; 265 | pool->num_slots = num_slots; 266 | pool->memory = (byte *)alloc(pool->backing_allocator, pool->memory_size); 267 | pool->slots_freelist = (int *)alloc(pool->backing_allocator, sizeof(int) * num_slots); 268 | pool->freelist_count = num_slots; 269 | pool->generations = (int *)alloc(pool->backing_allocator, sizeof(int) * num_slots); 270 | int slot_idx = 0; 271 | for (int idx = num_slots-1; idx >= 0; idx -= 1) { 272 | pool->slots_freelist[idx] = slot_idx; 273 | slot_idx += 1; 274 | } 275 | } 276 | 277 | void *pool_get(Pool_Allocator *pool, int *out_generation, int *out_index) { 278 | assert(pool->freelist_count > 0); 279 | int slot = pool->slots_freelist[pool->freelist_count-1]; 280 | pool->generations[slot] += 1; 281 | if (out_index) { 282 | *out_index = slot; 283 | } 284 | if (out_generation) { 285 | *out_generation = pool->generations[slot]; 286 | } 287 | pool->freelist_count -= 1; 288 | return memset(&pool->memory[pool->slot_size * slot], 0, pool->slot_size); 289 | } 290 | 291 | int pool_get_slot_index(Pool_Allocator *pool, void *ptr) { 292 | int slot = ((uintptr_t)ptr - (uintptr_t)pool->memory) / pool->slot_size; 293 | return slot; 294 | } 295 | 296 | void *pool_get_slot_by_index(Pool_Allocator *pool, int slot) { 297 | BOUNDS_CHECK(slot, 0, pool->num_slots); 298 | void *ptr = pool->memory + (pool->slot_size * slot); 299 | return ptr; 300 | } 301 | 302 | void pool_return(Pool_Allocator *pool, void *ptr) { 303 | assert((pool->freelist_count+1) <= pool->num_slots); 304 | int slot = pool_get_slot_index(pool, ptr); 305 | pool->slots_freelist[pool->freelist_count] = slot; 306 | pool->freelist_count += 1; 307 | } 308 | 309 | void *pool_alloc(void *allocator, int size, int align) { 310 | Pool_Allocator *pool = (Pool_Allocator *)allocator; 311 | assert(pool != nullptr); 312 | return pool_get(pool, nullptr, nullptr); 313 | } 314 | 315 | void pool_free(void *allocator, void *ptr) { 316 | Pool_Allocator *pool = (Pool_Allocator *)allocator; 317 | assert(pool != nullptr); 318 | pool_return(pool, ptr); 319 | } 320 | 321 | Allocator pool_allocator(Pool_Allocator *pool) { 322 | Allocator a = {}; 323 | a.data = pool; 324 | a.alloc_proc = pool_alloc; 325 | a.free_proc = pool_free; 326 | return a; 327 | } 328 | 329 | void destroy_pool(Pool_Allocator pool) { 330 | if (pool.memory) free(pool.backing_allocator, pool.memory); 331 | if (pool.slots_freelist) free(pool.backing_allocator, pool.slots_freelist); 332 | } 333 | 334 | 335 | 336 | // todo(josh): custom allocator 337 | char *read_entire_file(const char *filename, int *len) { 338 | FILE *file = fopen(filename, "rb"); 339 | if (file == nullptr) { 340 | return nullptr; 341 | } 342 | fseek(file, 0, SEEK_END); 343 | long length = ftell(file); 344 | fseek(file, 0, SEEK_SET); 345 | char *str = (char *)malloc(length + 1); 346 | fread(str, 1, length, file); 347 | fclose(file); 348 | 349 | str[length] = 0; 350 | *len = length+1; 351 | return str; 352 | } 353 | 354 | void write_entire_file(const char *filename, const char *data) { 355 | FILE *file = fopen(filename, "w"); 356 | assert(file != nullptr); 357 | fputs(data, file); 358 | fclose(file); 359 | } 360 | 361 | 362 | 363 | String_Builder make_string_builder(Allocator allocator, int capacity) { 364 | String_Builder sb = {}; 365 | sb.buf = make_array(allocator, capacity); 366 | return sb; 367 | } 368 | 369 | char *String_Builder::print(const char *str) { 370 | int length = strlen(str); 371 | buf.reserve(buf.count + length + 8); 372 | char *copy = &buf.data[buf.count]; 373 | memcpy(copy, str, length); 374 | buf.count += length; 375 | BOUNDS_CHECK(buf.count, 0, buf.capacity); 376 | buf.data[buf.count] = 0; 377 | return copy; 378 | } 379 | 380 | char *String_Builder::printf(const char *fmt, ...) { 381 | va_list args; 382 | buf.reserve(32); // ensure at least 32 bytes in the buffer 383 | int length_would_have_written = 0; 384 | do { 385 | buf.reserve(buf.count + length_would_have_written + 8); 386 | va_start(args, fmt); 387 | length_would_have_written = vsnprintf(&buf.data[buf.count], buf.capacity - buf.count, fmt, args); 388 | va_end(args); 389 | } while (length_would_have_written >= (buf.capacity - buf.count)); 390 | 391 | assert(length_would_have_written < buf.capacity - buf.count); 392 | char *copy = &buf.data[buf.count]; 393 | buf.data[buf.count+length_would_have_written+1] = 0; 394 | buf.count += length_would_have_written; 395 | BOUNDS_CHECK(buf.count, 0, buf.capacity); 396 | return copy; 397 | } 398 | 399 | char *String_Builder::write_with_length(const char *str, int length) { 400 | buf.reserve(buf.count + length + 8); 401 | char *copy = &buf.data[buf.count]; 402 | memcpy(copy, str, length); 403 | buf.count += length; 404 | BOUNDS_CHECK(buf.count, 0, buf.capacity); 405 | buf.data[buf.count] = 0; 406 | return copy; 407 | } 408 | 409 | void String_Builder::clear() { 410 | buf.clear(); 411 | BOUNDS_CHECK(buf.count, 0, buf.capacity); 412 | buf.data[buf.count] = 0; 413 | } 414 | 415 | char *String_Builder::string() { 416 | return buf.data; 417 | } 418 | 419 | void String_Builder::destroy() { 420 | buf.destroy(); 421 | } 422 | 423 | 424 | 425 | Chunked_String_Builder make_chunked_string_builder(Allocator allocator, int chunk_size) { 426 | Chunked_String_Builder sb = {}; 427 | sb.max_chunk_size = chunk_size; 428 | sb.chunks.allocator = allocator; 429 | sb.allocator = allocator; 430 | return sb; 431 | } 432 | 433 | Chunked_String_Builder_Chunk *Chunked_String_Builder::get_or_make_chunk_buffer_for_length(int length) { 434 | // todo(josh): should we double the chunk size when allocating new chunks? 435 | Chunked_String_Builder_Chunk *current_chunk = nullptr; 436 | assert(max_chunk_size > 0); 437 | if (chunks.count == 0) { 438 | current_chunk = chunks.append(); 439 | current_chunk->buffer = (char *)alloc(allocator, max_chunk_size, DEFAULT_ALIGNMENT, false); 440 | current_chunk->buffer[0] = 0; 441 | current_chunk->buffer_size = max_chunk_size; 442 | } 443 | else { 444 | current_chunk = &chunks[current_chunk_index]; 445 | } 446 | if (length > (current_chunk->buffer_size - 1 - current_chunk->cursor)) { // not enough room let in this chunk 447 | if (length >= max_chunk_size) { // this string is bigger than our max chunk size, copy the string as it's own chunk 448 | current_chunk = chunks.append(); 449 | current_chunk->buffer = (char *)alloc(allocator, length+1, DEFAULT_ALIGNMENT, false); 450 | current_chunk->buffer[0] = 0; 451 | current_chunk->buffer_size = length+1; 452 | current_chunk_index += 1; 453 | return current_chunk; 454 | } 455 | else { 456 | current_chunk = chunks.append(); 457 | current_chunk->buffer = (char *)alloc(allocator, max_chunk_size, DEFAULT_ALIGNMENT, false); 458 | current_chunk->buffer[0] = 0; 459 | current_chunk->buffer_size = max_chunk_size; 460 | current_chunk_index += 1; 461 | return current_chunk; 462 | } 463 | } 464 | else { 465 | return current_chunk; 466 | } 467 | } 468 | 469 | char *Chunked_String_Builder::print(const char *str) { 470 | int length = strlen(str); 471 | return write_with_length(str, length); 472 | } 473 | 474 | char *Chunked_String_Builder::printf(const char *fmt, ...) { 475 | va_list args; 476 | va_start(args, fmt); 477 | int length_required = vsnprintf(nullptr, 0, fmt, args); 478 | va_end(args); 479 | 480 | Chunked_String_Builder_Chunk *chunk = get_or_make_chunk_buffer_for_length(length_required); 481 | char *copy = &chunk->buffer[chunk->cursor]; 482 | va_start(args, fmt); 483 | int result = vsnprintf(copy, chunk->buffer_size - chunk->cursor, fmt, args); 484 | va_end(args); 485 | assert(result == length_required); 486 | chunk->cursor += length_required; 487 | chunk->buffer[chunk->cursor] = 0; 488 | return copy; 489 | } 490 | 491 | char *Chunked_String_Builder::write_with_length(const char *str, int length) { 492 | Chunked_String_Builder_Chunk *chunk = get_or_make_chunk_buffer_for_length(length); 493 | char *copy = &chunk->buffer[chunk->cursor]; 494 | memcpy(copy, str, length); 495 | chunk->cursor += length; 496 | chunk->buffer[chunk->cursor] = 0; 497 | return copy; 498 | } 499 | 500 | void Chunked_String_Builder::append_null() { 501 | Chunked_String_Builder_Chunk *chunk = get_or_make_chunk_buffer_for_length(1); 502 | chunk->buffer[chunk->cursor] = 0; 503 | chunk->cursor += 1; 504 | chunk->buffer[chunk->cursor] = 0; 505 | } 506 | 507 | void Chunked_String_Builder::clear() { 508 | For (idx, chunks) { 509 | chunks[idx].cursor = 0; 510 | chunks[idx].buffer[0] = 0; 511 | } 512 | current_chunk_index = 0; 513 | } 514 | 515 | char *Chunked_String_Builder::make_string() { 516 | int required_length = 0; 517 | for (int idx = 0; idx <= current_chunk_index && idx < chunks.count; idx += 1) { 518 | Chunked_String_Builder_Chunk chunk = chunks[idx]; 519 | required_length += chunk.cursor; 520 | } 521 | required_length += 1; // null term 522 | char *big_buffer = (char *)alloc(allocator, required_length, DEFAULT_ALIGNMENT, false); 523 | int current_cursor = 0; 524 | for (int idx = 0; idx <= current_chunk_index && idx < chunks.count; idx += 1) { 525 | Chunked_String_Builder_Chunk chunk = chunks[idx]; 526 | memcpy(&big_buffer[current_cursor], chunk.buffer, chunk.cursor); 527 | current_cursor += chunk.cursor; 528 | } 529 | assert(current_cursor+1 == required_length); 530 | big_buffer[current_cursor] = 0; 531 | return big_buffer; 532 | } 533 | 534 | void Chunked_String_Builder::destroy() { 535 | For (idx, chunks) { 536 | free(allocator, chunks[idx].buffer); 537 | } 538 | chunks.destroy(); 539 | } 540 | 541 | 542 | 543 | bool starts_with(const char *str, const char *start) { 544 | for (int i = 0; start[i] != 0; i++) { 545 | if (str[i] != start[i]) { 546 | return false; 547 | } 548 | } 549 | return true; 550 | } 551 | 552 | bool ends_with(const char *str, const char *end) { 553 | int str_length = strlen(str); 554 | int end_length = strlen(end); 555 | if (str_length < end_length) { 556 | return false; 557 | } 558 | for (int i = 0; i < end_length; i++) { 559 | if (str[str_length-1-i] != end[end_length-1-i]) { 560 | return false; 561 | } 562 | } 563 | return true; 564 | } 565 | 566 | int last_index_of(const char *str, const char *query) { 567 | int end_of_str = strlen(str)-1; 568 | int query_len = strlen(query)-1; 569 | for (int i = end_of_str-query_len; i >= 0; i -= 1) { 570 | const char *c = &str[i]; 571 | bool equal = true; 572 | for (int q = 0; q < query_len; q += 1) { 573 | if (str[i+q] != query[q]) { 574 | equal = false; 575 | } 576 | } 577 | if (equal) { 578 | return i; 579 | } 580 | } 581 | return -1; 582 | } 583 | 584 | // path/to/file.txt -> path/to 585 | // file.txt -> 586 | // returns null if it doesn't hit a '/' or '\\' 587 | char *path_directory(const char *filepath, Allocator allocator) { 588 | int length = strlen(filepath); 589 | int slash_index = length; 590 | for (; slash_index >= 0; slash_index--) { 591 | if (filepath[slash_index] == '/' || filepath[slash_index] == '\\') { 592 | break; 593 | } 594 | } 595 | if (slash_index == -1) { 596 | return nullptr; 597 | } 598 | int length_to_end = length - (length - slash_index); 599 | char *new_str = (char *)alloc(allocator, length_to_end+1, DEFAULT_ALIGNMENT, false); 600 | memcpy(new_str, filepath, length_to_end); 601 | new_str[length_to_end] = 0; 602 | return new_str; 603 | } 604 | 605 | // path/to/file.txt -> file 606 | // file.txt -> file 607 | // file -> file 608 | // path/to/ -> 609 | char *path_filename(const char *filepath, Allocator allocator) { 610 | int length = strlen(filepath); 611 | int slash_index = length; 612 | int dot_index = length; 613 | for (; slash_index >= 0; slash_index--) { 614 | if (filepath[slash_index] == '/' || filepath[slash_index] == '\\') { 615 | break; 616 | } 617 | } 618 | for (; dot_index >= 0; dot_index--) { 619 | if (filepath[dot_index] == '.') { 620 | break; 621 | } 622 | } 623 | int start = slash_index+1; 624 | int end = dot_index; 625 | if (end < start) { 626 | end = length; 627 | } 628 | if (start == end) { 629 | return nullptr; 630 | } 631 | assert(end >= start); 632 | int length_to_end = end - start; 633 | char *new_str = (char *)alloc(allocator, length_to_end+1, DEFAULT_ALIGNMENT, false); 634 | memcpy(new_str, &filepath[start], length_to_end); 635 | new_str[length_to_end] = 0; 636 | return new_str; 637 | } 638 | 639 | // path/to/file.txt -> file.txt 640 | // file.txt -> file.txt 641 | // file -> file 642 | // path/to/ -> 643 | char *path_filename_with_extension(const char *filepath, Allocator allocator) { 644 | int length = strlen(filepath); 645 | int slash_index = length; 646 | for (; slash_index >= 0; slash_index--) { 647 | if (filepath[slash_index] == '/' || filepath[slash_index] == '\\') { 648 | break; 649 | } 650 | } 651 | int start = slash_index+1; 652 | int end = length; 653 | if (start == end) { 654 | return nullptr; 655 | } 656 | assert(end >= start); 657 | int length_to_end = end - start; 658 | char *new_str = (char *)alloc(allocator, length_to_end+1, DEFAULT_ALIGNMENT, false); 659 | memcpy(new_str, &filepath[start], length_to_end); 660 | new_str[length_to_end] = 0; 661 | return new_str; 662 | } -------------------------------------------------------------------------------- /src/c_backend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "parser.h" 4 | #include "checker.h" 5 | #include "basic.h" 6 | 7 | void init_c_backend(); 8 | Chunked_String_Builder generate_c_main_file(Ast_Block *global_scope); -------------------------------------------------------------------------------- /src/checker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "os_windows.h" 6 | #include "common.h" 7 | #include "parser.h" 8 | 9 | #include "basic.h" 10 | 11 | enum Type_Kind { 12 | TYPE_INVALID, 13 | TYPE_PRIMITIVE, 14 | TYPE_TUPLE, 15 | TYPE_STRUCT, 16 | TYPE_ENUM, 17 | TYPE_POINTER, 18 | TYPE_REFERENCE, 19 | TYPE_ARRAY, 20 | TYPE_POLYMORPHIC, 21 | TYPE_SLICE, 22 | TYPE_VARARGS, 23 | TYPE_PROCEDURE, 24 | 25 | TYPE_COUNT, 26 | }; 27 | 28 | enum Type_Flags { 29 | TF_INTEGER = 1 << 0, 30 | TF_FLOAT = 1 << 1, 31 | TF_SIGNED = 1 << 2, 32 | TF_UNSIGNED = 1 << 3, 33 | TF_UNTYPED = 1 << 4, 34 | TF_NUMBER = 1 << 5, 35 | TF_POINTER = 1 << 6, 36 | TF_POLYMORPHIC = 1 << 7, 37 | TF_STRING = 1 << 8, 38 | }; 39 | 40 | enum Type_Completion_Flags { 41 | TCF_SIZE = 1 << 0, 42 | TCF_USINGS = 1 << 1, 43 | TCF_GENERATE_FIELDS = 1 << 2, // note(josh): this one is done automatically, no need to pass it to complete_type() 44 | TCF_ADD_ORDERED_DECLARATION = 1 << 3, // note(josh): this one is done automatically, no need to pass it to complete_type() 45 | TCF_ALL = TCF_SIZE | TCF_USINGS | TCF_GENERATE_FIELDS | TCF_ADD_ORDERED_DECLARATION, 46 | }; 47 | 48 | struct Type_Pointer; 49 | struct Type_Slice; 50 | struct Type_Varargs; 51 | struct Type_Reference; 52 | 53 | struct Struct_Field { 54 | const char *name = {}; 55 | Operand operand = {}; 56 | int offset = {}; // -1 if is_constant 57 | Array notes = {}; 58 | }; 59 | 60 | struct Type { 61 | i64 id = {}; 62 | Type_Kind kind = {}; 63 | int size = {}; 64 | int align = {}; 65 | u64 flags = {}; 66 | u64 completed_flags = {}; // Type_Completion_Flags 67 | u64 currently_completing_flags = {}; // Type_Completion_Flags 68 | Array all_fields = {}; 69 | Array constant_fields = {}; 70 | Array variable_fields = {}; 71 | Ast_Block *declarations_block = {}; 72 | char *printable_name = {}; 73 | int printable_name_length = {}; // note(josh): doesn't include null term 74 | Type_Pointer *pointer_to_this_type = {}; 75 | Type_Slice *slice_of_this_type = {}; 76 | Type_Varargs *varargs_of_this_type = {}; 77 | Type_Varargs *c_varargs_of_this_type = {}; 78 | Type_Reference *reference_to_this_type = {}; 79 | Type(Type_Kind kind) 80 | : kind(kind) 81 | { 82 | all_fields.allocator = g_global_linear_allocator; 83 | constant_fields.allocator = g_global_linear_allocator; 84 | variable_fields.allocator = g_global_linear_allocator; 85 | } 86 | }; 87 | 88 | struct Type_Primitive : public Type { 89 | char *name = nullptr; 90 | Type_Primitive(char *name, int size, int align) 91 | : Type(TYPE_PRIMITIVE) 92 | , name(name) 93 | { 94 | this->size = size; 95 | this->align = align; 96 | } 97 | }; 98 | 99 | struct Type_Tuple : public Type { 100 | Arraytypes = {}; 101 | Type_Tuple(Arraytypes) 102 | : Type(TYPE_TUPLE) 103 | , types(types) 104 | {} 105 | }; 106 | 107 | struct Type_Struct : public Type { 108 | char *name = nullptr; 109 | Ast_Struct *ast_struct = {}; 110 | Ast_Struct *is_polymorph_of = {}; 111 | Array polymorphic_parameter_values = {}; 112 | bool is_union = {}; 113 | Array notes = {}; 114 | Type_Struct(Ast_Struct *structure) 115 | : Type(TYPE_STRUCT) 116 | , name(structure->name) 117 | , ast_struct(structure) 118 | , notes(structure->declaration->notes) 119 | { 120 | polymorphic_parameter_values.allocator = g_global_linear_allocator; 121 | } 122 | }; 123 | 124 | struct Type_Enum : public Type { 125 | const char *name = nullptr; 126 | Type *base_type = {}; 127 | Array notes = {}; 128 | Type_Enum(const char *name, Array notes) 129 | : Type(TYPE_ENUM) 130 | , name(name) 131 | , notes(notes) 132 | { 133 | flags |= TF_INTEGER; 134 | } 135 | }; 136 | 137 | struct Type_Pointer : public Type { 138 | Type *pointer_to = {}; 139 | Type_Pointer(Type *pointer_to) 140 | : Type(TYPE_POINTER) 141 | , pointer_to(pointer_to) 142 | {} 143 | }; 144 | 145 | struct Type_Reference : public Type { 146 | Type *reference_to = {}; 147 | Type_Reference(Type *reference_to) 148 | : Type(TYPE_REFERENCE) 149 | , reference_to(reference_to) 150 | {} 151 | }; 152 | 153 | struct Type_Array : public Type { 154 | Type *array_of = {}; 155 | int count = {}; 156 | Type_Array(Type *array_of, int count) 157 | : Type(TYPE_ARRAY) 158 | , array_of(array_of) 159 | , count(count) 160 | {} 161 | }; 162 | 163 | struct Type_Slice : public Type { 164 | Type *slice_of = {}; 165 | Type *data_pointer_type = {}; 166 | Type_Slice(Type *slice_of, Type *data_pointer_type) 167 | : Type(TYPE_SLICE) 168 | , slice_of(slice_of) 169 | , data_pointer_type(data_pointer_type) 170 | {} 171 | }; 172 | 173 | struct Type_Varargs : public Type { 174 | Type *varargs_of = {}; 175 | Type_Pointer *data_pointer_type = {}; 176 | Type_Slice *slice_type = {}; 177 | bool is_c_varargs = {}; 178 | Type_Varargs(Type *varargs_of, Type_Pointer *data_pointer_type, Type_Slice *slice_type, bool is_c_varargs) 179 | : Type(TYPE_VARARGS) 180 | , varargs_of(varargs_of) 181 | , slice_type(slice_type) 182 | , data_pointer_type(data_pointer_type) 183 | , is_c_varargs(is_c_varargs) 184 | {} 185 | }; 186 | 187 | struct Type_Procedure : public Type { 188 | Array parameter_types = {}; 189 | Type *return_type = {}; 190 | Type_Procedure(Array parameter_types, Type *return_type) 191 | : Type(TYPE_PROCEDURE) 192 | , parameter_types(parameter_types) 193 | , return_type(return_type) 194 | {} 195 | }; 196 | 197 | extern Array ordered_declarations; 198 | extern Array all_types; 199 | extern Type *type_i8; 200 | extern Type *type_i16; 201 | extern Type *type_i32; 202 | extern Type *type_i64; 203 | extern Type *type_u8; 204 | extern Type *type_u16; 205 | extern Type *type_u32; 206 | extern Type *type_u64; 207 | extern Type *type_f32; 208 | extern Type *type_f64; 209 | extern Type *type_bool; 210 | extern Type *type_untyped_number; 211 | extern Type *type_untyped_null; 212 | extern Type *type_typeid; 213 | extern Type *type_cstring; 214 | extern Type *type_string; 215 | extern Type *type_rawptr; 216 | extern Type *type_byte; 217 | extern Type *type_int; 218 | extern Type *type_uint; 219 | extern Type *type_float; 220 | extern Type *type_any; 221 | 222 | extern Array all_types; 223 | extern Array g_completed_types; 224 | 225 | extern Ast_Proc *g_main_proc; 226 | 227 | extern Spinlock g_ordered_declarations_spinlock; 228 | extern Spinlock g_completed_types_spinlock; 229 | 230 | extern bool g_done_typechecking; 231 | extern bool g_done_typechecking_sif_runtime_stuff; 232 | 233 | extern Declaration *sif_runtime_bounds_check_proc; 234 | extern Declaration *sif_runtime_null_check_proc; 235 | extern Declaration *sif_runtime_string_eq_proc; 236 | extern Declaration *sif_runtime_zero_pointer_proc; 237 | extern Declaration *sif_runtime_source_code_location; 238 | extern Declaration *sif_runtime_make_source_code_location; 239 | extern Struct_Declaration *sif_runtime_union_all_type_infos; 240 | extern Struct_Declaration *sif_runtime_type_info; 241 | extern Struct_Declaration *sif_runtime_type_info_integer; 242 | extern Struct_Declaration *sif_runtime_type_info_float; 243 | extern Struct_Declaration *sif_runtime_type_info_bool; 244 | extern Struct_Declaration *sif_runtime_type_info_string; 245 | extern Struct_Declaration *sif_runtime_type_info_struct_field; 246 | extern Struct_Declaration *sif_runtime_type_info_struct; 247 | extern Struct_Declaration *sif_runtime_type_info_union; 248 | extern Struct_Declaration *sif_runtime_type_info_enum_field; 249 | extern Struct_Declaration *sif_runtime_type_info_enum; 250 | extern Struct_Declaration *sif_runtime_type_info_pointer; 251 | extern Struct_Declaration *sif_runtime_type_info_slice; 252 | extern Struct_Declaration *sif_runtime_type_info_array; 253 | extern Struct_Declaration *sif_runtime_type_info_reference; 254 | extern Struct_Declaration *sif_runtime_type_info_procedure; 255 | extern Struct_Declaration *sif_runtime_type_info_typeid; 256 | 257 | void init_checker(); 258 | void add_global_declarations(Ast_Block *block); 259 | bool typecheck_global_scope(Ast_Block *block); 260 | 261 | bool is_type_pointer (Type *type); 262 | bool is_type_procedure (Type *type); 263 | bool is_type_polymorphic(Type *type); 264 | bool is_type_reference (Type *type); 265 | bool is_type_array (Type *type); 266 | bool is_type_slice (Type *type); 267 | bool is_type_number (Type *type); 268 | bool is_type_integer (Type *type); 269 | bool is_type_float (Type *type); 270 | bool is_type_bool (Type *type); 271 | bool is_type_untyped (Type *type); 272 | bool is_type_unsigned (Type *type); 273 | bool is_type_signed (Type *type); 274 | bool is_type_struct (Type *type); 275 | bool is_type_incomplete (Type *type); 276 | bool is_type_typeid (Type *type); 277 | bool is_type_string (Type *type); 278 | bool is_type_varargs (Type *type); 279 | bool is_type_enum (Type *type); 280 | 281 | char *type_to_string(Type *type, int *out_length = nullptr); 282 | Type_Pointer *get_or_create_type_pointer_to(Type *type); 283 | Type_Reference *get_or_create_type_reference_to(Type *type); 284 | Type_Array *get_or_create_type_array_of(Type *type, int count); 285 | Type_Slice *get_or_create_type_slice_of(Type *type); 286 | Type_Varargs *get_or_create_type_varargs_of(Type *type, bool is_c_varargs); 287 | bool complete_type(Type *type); 288 | Ast_Expr *unparen_expr(Ast_Expr *expr); 289 | -------------------------------------------------------------------------------- /src/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | Hashtable g_interned_strings; 4 | 5 | Spinlock g_intern_strings_spinlock; 6 | 7 | char *intern_string(char *str, int length_override) { 8 | u64 hash = 0xcbf29ce484222325; 9 | int length = length_override; 10 | if (length_override == -1) { 11 | char *c = str; 12 | for (; *c != '\0'; c++) { 13 | hash = (hash * 0x100000001b3) ^ u64(*c); 14 | } 15 | length = c - str; 16 | } 17 | else { 18 | for (int i = 0; i < length_override; i++) { 19 | hash = (hash * 0x100000001b3) ^ u64(str[i]); 20 | } 21 | } 22 | g_intern_strings_spinlock.lock(); 23 | defer(g_intern_strings_spinlock.unlock()); 24 | char **interned = g_interned_strings.get(hash); 25 | if (interned) { 26 | return *interned; 27 | } 28 | g_interned_strings.insert(hash, str); 29 | return str; 30 | } 31 | 32 | char *g_interned_string_return; 33 | char *g_interned_string_const; 34 | char *g_interned_string_proc; 35 | char *g_interned_string_operator; 36 | char *g_interned_string_struct; 37 | char *g_interned_string_union; 38 | char *g_interned_string_enum; 39 | char *g_interned_string_null; 40 | char *g_interned_string_true; 41 | char *g_interned_string_false; 42 | char *g_interned_string_sizeof; 43 | char *g_interned_string_typeof; 44 | char *g_interned_string_typeofelement; 45 | char *g_interned_string_if; 46 | char *g_interned_string_else; 47 | char *g_interned_string_for; 48 | char *g_interned_string_while; 49 | char *g_interned_string_break; 50 | char *g_interned_string_continue; 51 | char *g_interned_string_cast; 52 | char *g_interned_string_transmute; 53 | char *g_interned_string_using; 54 | char *g_interned_string_defer; 55 | 56 | char *g_interned_main_string; 57 | char *g_interned_sif_runtime_string; 58 | char *g_interned_string_string; 59 | char *g_interned_cstring_string; 60 | char *g_interned_rawptr_string; 61 | char *g_interned_any_string; 62 | char *g_interned_typeid_string; 63 | char *g_interned_data_string; 64 | char *g_interned_type_string; 65 | char *g_interned_count_string; 66 | 67 | char *g_interned_sif_runtime_bounds_check_proc_string; 68 | char *g_interned_sif_runtime_null_check_proc_string; 69 | char *g_interned_sif_runtime_string_eq_proc_string; 70 | char *g_interned_sif_runtime_zero_pointer_proc_string; 71 | char *g_interned_sif_runtime_source_code_location_string; 72 | char *g_interned_sif_runtime_make_source_code_location_string; 73 | 74 | char *g_interned_sif_runtime_union_all_type_infos_string; 75 | char *g_interned_sif_runtime_type_info_string; 76 | char *g_interned_sif_runtime_type_info_integer_string; 77 | char *g_interned_sif_runtime_type_info_float_string; 78 | char *g_interned_sif_runtime_type_info_bool_string; 79 | char *g_interned_sif_runtime_type_info_string_string; 80 | char *g_interned_sif_runtime_type_info_struct_field_string; 81 | char *g_interned_sif_runtime_type_info_struct_string; 82 | char *g_interned_sif_runtime_type_info_union_string; 83 | char *g_interned_sif_runtime_type_info_enum_field_string; 84 | char *g_interned_sif_runtime_type_info_enum_string; 85 | char *g_interned_sif_runtime_type_info_pointer_string; 86 | char *g_interned_sif_runtime_type_info_slice_string; 87 | char *g_interned_sif_runtime_type_info_array_string; 88 | char *g_interned_sif_runtime_type_info_reference_string; 89 | char *g_interned_sif_runtime_type_info_procedure_string; 90 | char *g_interned_sif_runtime_type_info_typeid_string; 91 | 92 | void init_interned_strings() { 93 | g_interned_strings = make_hashtable(g_global_linear_allocator, 10 * 1024); 94 | g_interned_string_return = intern_string("return"); 95 | g_interned_string_const = intern_string("const"); 96 | g_interned_string_proc = intern_string("proc"); 97 | g_interned_string_operator = intern_string("operator"); 98 | g_interned_string_struct = intern_string("struct"); 99 | g_interned_string_union = intern_string("union"); 100 | g_interned_string_enum = intern_string("enum"); 101 | g_interned_string_null = intern_string("null"); 102 | g_interned_string_true = intern_string("true"); 103 | g_interned_string_false = intern_string("false"); 104 | g_interned_string_sizeof = intern_string("sizeof"); 105 | g_interned_string_typeof = intern_string("typeof"); 106 | g_interned_string_typeofelement = intern_string("typeofelement"); 107 | g_interned_string_if = intern_string("if"); 108 | g_interned_string_else = intern_string("else"); 109 | g_interned_string_for = intern_string("for"); 110 | g_interned_string_while = intern_string("while"); 111 | g_interned_string_break = intern_string("break"); 112 | g_interned_string_continue = intern_string("continue"); 113 | g_interned_string_cast = intern_string("cast"); 114 | g_interned_string_transmute = intern_string("transmute"); 115 | g_interned_string_using = intern_string("using"); 116 | g_interned_string_defer = intern_string("defer"); 117 | 118 | g_interned_main_string = intern_string("main"); 119 | g_interned_sif_runtime_string = intern_string("sif_runtime"); 120 | g_interned_string_string = intern_string("string"); 121 | g_interned_cstring_string = intern_string("cstring"); 122 | g_interned_rawptr_string = intern_string("rawptr"); 123 | g_interned_any_string = intern_string("any"); 124 | g_interned_typeid_string = intern_string("typeid"); 125 | g_interned_data_string = intern_string("data"); 126 | g_interned_type_string = intern_string("type"); 127 | g_interned_count_string = intern_string("count"); 128 | 129 | g_interned_sif_runtime_bounds_check_proc_string = intern_string("sif_bounds_check"); 130 | g_interned_sif_runtime_null_check_proc_string = intern_string("sif_null_check"); 131 | g_interned_sif_runtime_string_eq_proc_string = intern_string("string_eq"); 132 | g_interned_sif_runtime_zero_pointer_proc_string = intern_string("zero_pointer"); 133 | g_interned_sif_runtime_source_code_location_string = intern_string("Source_Code_Location"); 134 | g_interned_sif_runtime_make_source_code_location_string = intern_string("make_source_code_location"); 135 | 136 | g_interned_sif_runtime_union_all_type_infos_string = intern_string("Union_All_Type_Infos"); 137 | g_interned_sif_runtime_type_info_string = intern_string("Type_Info"); 138 | g_interned_sif_runtime_type_info_integer_string = intern_string("Type_Info_Integer"); 139 | g_interned_sif_runtime_type_info_float_string = intern_string("Type_Info_Float"); 140 | g_interned_sif_runtime_type_info_bool_string = intern_string("Type_Info_Bool"); 141 | g_interned_sif_runtime_type_info_string_string = intern_string("Type_Info_String"); 142 | g_interned_sif_runtime_type_info_struct_field_string = intern_string("Type_Info_Struct_Field"); 143 | g_interned_sif_runtime_type_info_struct_string = intern_string("Type_Info_Struct"); 144 | g_interned_sif_runtime_type_info_union_string = intern_string("Type_Info_Union"); 145 | g_interned_sif_runtime_type_info_enum_field_string = intern_string("Type_Info_Enum_Field"); 146 | g_interned_sif_runtime_type_info_enum_string = intern_string("Type_Info_Enum"); 147 | g_interned_sif_runtime_type_info_pointer_string = intern_string("Type_Info_Pointer"); 148 | g_interned_sif_runtime_type_info_slice_string = intern_string("Type_Info_Slice"); 149 | g_interned_sif_runtime_type_info_array_string = intern_string("Type_Info_Array"); 150 | g_interned_sif_runtime_type_info_reference_string = intern_string("Type_Info_Reference"); 151 | g_interned_sif_runtime_type_info_procedure_string = intern_string("Type_Info_Procedure"); 152 | g_interned_sif_runtime_type_info_typeid_string = intern_string("Type_Info_Typeid"); 153 | } -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "basic.h" 4 | #include "os_windows.h" 5 | 6 | extern char *sif_core_lib_path; 7 | extern Allocator g_global_linear_allocator; 8 | extern bool g_logged_error; 9 | extern bool g_no_threads; 10 | extern bool g_is_debug_build; 11 | 12 | #define SIF_NEW(T, allocator) ((T *)alloc(allocator, sizeof(T), true)) 13 | 14 | template 15 | T *SIF_NEW_CLONE(T t, Allocator allocator) { 16 | T *ptr = SIF_NEW(T, allocator); 17 | *ptr = t; 18 | return ptr; 19 | } 20 | 21 | extern char *g_interned_string_return; 22 | extern char *g_interned_string_const; 23 | extern char *g_interned_string_proc; 24 | extern char *g_interned_string_operator; 25 | extern char *g_interned_string_struct; 26 | extern char *g_interned_string_union; 27 | extern char *g_interned_string_enum; 28 | extern char *g_interned_string_null; 29 | extern char *g_interned_string_true; 30 | extern char *g_interned_string_false; 31 | extern char *g_interned_string_sizeof; 32 | extern char *g_interned_string_typeof; 33 | extern char *g_interned_string_typeofelement; 34 | extern char *g_interned_string_if; 35 | extern char *g_interned_string_else; 36 | extern char *g_interned_string_for; 37 | extern char *g_interned_string_while; 38 | extern char *g_interned_string_break; 39 | extern char *g_interned_string_continue; 40 | extern char *g_interned_string_cast; 41 | extern char *g_interned_string_transmute; 42 | extern char *g_interned_string_using; 43 | extern char *g_interned_string_defer; 44 | 45 | extern char *g_interned_main_string; 46 | extern char *g_interned_sif_runtime_string; 47 | extern char *g_interned_string_string; 48 | extern char *g_interned_cstring_string; 49 | extern char *g_interned_rawptr_string; 50 | extern char *g_interned_any_string; 51 | extern char *g_interned_typeid_string; 52 | extern char *g_interned_data_string; 53 | extern char *g_interned_type_string; 54 | extern char *g_interned_count_string; 55 | 56 | extern char *g_interned_sif_runtime_bounds_check_proc_string; 57 | extern char *g_interned_sif_runtime_null_check_proc_string; 58 | extern char *g_interned_sif_runtime_string_eq_proc_string; 59 | extern char *g_interned_sif_runtime_zero_pointer_proc_string; 60 | extern char *g_interned_sif_runtime_source_code_location_string; 61 | extern char *g_interned_sif_runtime_make_source_code_location_string; 62 | 63 | extern char *g_interned_sif_runtime_union_all_type_infos_string; 64 | extern char *g_interned_sif_runtime_type_info_string; 65 | extern char *g_interned_sif_runtime_type_info_integer_string; 66 | extern char *g_interned_sif_runtime_type_info_float_string; 67 | extern char *g_interned_sif_runtime_type_info_bool_string; 68 | extern char *g_interned_sif_runtime_type_info_string_string; 69 | extern char *g_interned_sif_runtime_type_info_struct_field_string; 70 | extern char *g_interned_sif_runtime_type_info_struct_string; 71 | extern char *g_interned_sif_runtime_type_info_union_string; 72 | extern char *g_interned_sif_runtime_type_info_enum_field_string; 73 | extern char *g_interned_sif_runtime_type_info_enum_string; 74 | extern char *g_interned_sif_runtime_type_info_pointer_string; 75 | extern char *g_interned_sif_runtime_type_info_slice_string; 76 | extern char *g_interned_sif_runtime_type_info_array_string; 77 | extern char *g_interned_sif_runtime_type_info_reference_string; 78 | extern char *g_interned_sif_runtime_type_info_procedure_string; 79 | extern char *g_interned_sif_runtime_type_info_typeid_string; 80 | 81 | void init_interned_strings(); 82 | char *intern_string(char *str, int length_override = -1); -------------------------------------------------------------------------------- /src/ir.cpp: -------------------------------------------------------------------------------- 1 | #include "ir.h" 2 | #include "basic.h" 3 | #include "common.h" 4 | 5 | static int g_ir_temporaries; 6 | 7 | IR_Temporary *ir_temporary(IR *ir) { 8 | IR_Temporary *t = SIF_NEW(IR_Temporary, g_global_linear_allocator); 9 | t->reg = g_ir_temporaries; 10 | g_ir_temporaries += 1; 11 | return t; 12 | } 13 | 14 | IR_Register_Storage *ir_register_storage(IR *ir, Type *type) { 15 | IR_Temporary *t = ir_temporary(ir); 16 | return SIF_NEW_CLONE(IR_Register_Storage(t, type), g_global_linear_allocator); 17 | } 18 | 19 | void ir_add_statement(IR *ir, IR_Statement *statement) { 20 | ir->current_proc->statements.append(statement); 21 | } 22 | 23 | void ir_store(IR *ir, IR_Storage *dst, IR_Storage *src) { 24 | IR_Statement_Store *store = SIF_NEW_CLONE(IR_Statement_Store(), g_global_linear_allocator); 25 | store->dst = dst; 26 | store->src = src; 27 | ir_add_statement(ir, store); 28 | } 29 | 30 | IR_Register_Storage *ir_load(IR *ir, IR_Storage *src) { 31 | IR_Register_Storage *dst = ir_register_storage(ir, src->type); 32 | IR_Statement_Load *load = SIF_NEW_CLONE(IR_Statement_Load(), g_global_linear_allocator); 33 | load->dst = dst; 34 | load->src = src; 35 | ir_add_statement(ir, load); 36 | return dst; 37 | } 38 | 39 | IR_Storage *ir_emit_expr(IR *ir, Ast_Expr *expr) { 40 | if (expr->operand.flags & OPERAND_CONSTANT) { 41 | assert(!is_type_untyped(expr->operand.type)); 42 | IR_Statement_Move_Constant *move = SIF_NEW_CLONE(IR_Statement_Move_Constant(), g_global_linear_allocator); 43 | move->operand = expr->operand; 44 | move->result = ir_register_storage(ir, expr->operand.type); 45 | ir_add_statement(ir, move); 46 | return move->result; 47 | } 48 | else { 49 | switch (expr->expr_kind) { 50 | case EXPR_BINARY: { 51 | Expr_Binary *binary = (Expr_Binary *)expr; 52 | IR_Storage *lhs = ir_emit_expr(ir, binary->lhs); 53 | IR_Storage *rhs = ir_emit_expr(ir, binary->rhs); 54 | IR_Statement_Binary *statement = SIF_NEW_CLONE(IR_Statement_Binary(), g_global_linear_allocator); 55 | statement->kind = IRS_BINARY; 56 | statement->lhs = lhs; 57 | statement->rhs = rhs; 58 | statement->result = ir_register_storage(ir, binary->operand.type); 59 | switch (binary->op) { 60 | case TK_PLUS: statement->op = IRO_ADD; break; 61 | case TK_MINUS: statement->op = IRO_SUB; break; 62 | case TK_MULTIPLY: statement->op = IRO_MUL; break; 63 | case TK_DIVIDE: statement->op = IRO_DIV; break; 64 | default: { 65 | printf("Unhandled op %d\n", binary->op); 66 | assert(false); 67 | } 68 | } 69 | ir_add_statement(ir, statement); 70 | return statement->result; 71 | } 72 | case EXPR_IDENTIFIER: { 73 | Expr_Identifier *ident = (Expr_Identifier *)expr; 74 | if (is_type_integer(ident->operand.type)) { 75 | assert(ident->operand.referenced_declaration->kind == DECL_VAR); 76 | Ast_Var *var = ((Var_Declaration *)ident->operand.referenced_declaration)->var; 77 | return var->ir_var->storage; 78 | } 79 | else { 80 | assert(false); 81 | } 82 | } 83 | case EXPR_ADDRESS_OF: { 84 | Expr_Address_Of *address = (Expr_Address_Of *)expr; 85 | assert(address->rhs->operand.flags & OPERAND_LVALUE); 86 | IR_Register_Storage *result = ir_register_storage(ir, address->operand.type); 87 | return result; 88 | } 89 | default: { 90 | printf("Unhandled expr type in ir_emit_expr(): %d\n", expr->expr_kind); 91 | assert(false); 92 | } 93 | } 94 | } 95 | printf("unreachable\n"); 96 | assert(false); 97 | return nullptr; 98 | } 99 | 100 | void ir_emit_block(IR *ir, Ast_Block *block) { 101 | For (idx, block->nodes) { 102 | Ast_Node *node = block->nodes[idx]; 103 | switch (node->ast_kind) { 104 | case AST_VAR: { 105 | Ast_Var *var = (Ast_Var *)node; 106 | if (var->expr) { 107 | IR_Storage *result = ir_emit_expr(ir, var->expr); 108 | ir_store(ir, var->ir_var->storage, result); 109 | } 110 | break; 111 | } 112 | case AST_ASSIGN: { 113 | Ast_Assign *assign = (Ast_Assign *)node; 114 | break; 115 | } 116 | case AST_STRUCT: break; 117 | case AST_PROC: break; 118 | case AST_ENUM: break; 119 | default: { 120 | printf("Unhandled node_kind in generate_ir_proc(): %d\n", node->ast_kind); 121 | assert(false); 122 | } 123 | } 124 | } 125 | } 126 | 127 | IR_Proc *generate_ir_proc(IR *ir, Ast_Proc *procedure) { 128 | IR_Proc *ir_proc = SIF_NEW_CLONE(IR_Proc(), g_global_linear_allocator); 129 | assert(ir->current_proc == nullptr); 130 | ir->current_proc = ir_proc; 131 | defer(ir->current_proc = nullptr); 132 | 133 | For (idx, procedure->header->parameters) { 134 | Ast_Var *ast_var = procedure->header->parameters[idx]; 135 | IR_Var *ir_var = SIF_NEW_CLONE(IR_Var(), g_global_linear_allocator); 136 | ast_var->ir_var = ir_var; 137 | ir_var->type = ast_var->type; 138 | ir_var->storage = SIF_NEW_CLONE(IR_Stack_Storage(ir_proc, ir_var->type), g_global_linear_allocator); 139 | ir_proc->parameters.append(ir_var); 140 | ir_proc->all_variables.append(ir_var); 141 | } 142 | 143 | For (idx, procedure->header->local_variables) { 144 | Ast_Var *ast_var = procedure->header->local_variables[idx]; 145 | IR_Var *ir_var = SIF_NEW_CLONE(IR_Var(), g_global_linear_allocator); 146 | ast_var->ir_var = ir_var; 147 | ir_var->type = ast_var->type; 148 | ir_var->storage = SIF_NEW_CLONE(IR_Stack_Storage(ir_proc, ir_var->type), g_global_linear_allocator); 149 | ir_proc->all_variables.append(ir_var); 150 | } 151 | 152 | ir_emit_block(ir, procedure->body); 153 | return ir_proc; 154 | } 155 | 156 | void printf_ir_statement(IR_Statement *statement) { 157 | switch (statement->kind) { 158 | case IRS_BINARY: { 159 | IR_Statement_Binary *stmt = (IR_Statement_Binary *)statement; 160 | printf("unimplemented printf IRS_BINARY"); 161 | // printf("IRS_BINARY r%d ", stmt->result->reg); 162 | // switch (stmt->op) { 163 | // case IRO_ADD: printf("ADD "); break; 164 | // case IRO_SUB: printf("SUB "); break; 165 | // case IRO_MUL: printf("MUL "); break; 166 | // case IRO_DIV: printf("DIV "); break; 167 | // default: { 168 | // printf("", stmt->op); 169 | // } 170 | // } 171 | // printf("r%d r%d", stmt->lhs->reg, stmt->rhs->reg); 172 | break; 173 | } 174 | case IRS_MOVE_CONSTANT: { 175 | IR_Statement_Move_Constant *stmt = (IR_Statement_Move_Constant *)statement; 176 | printf("unimplemented printf IRS_MOVE_CONSTANT"); 177 | // printf("IRS_MOVE_CONSTANT r%d ", stmt->result->reg); 178 | // if (is_type_integer(stmt->operand.type)) { 179 | // if (is_type_signed(stmt->operand.type)) { 180 | // printf("%lld", stmt->operand.int_value); 181 | // } 182 | // else { 183 | // printf("%llu", stmt->operand.uint_value); 184 | // } 185 | // } 186 | // else { 187 | // assert(false); 188 | // } 189 | break; 190 | } 191 | case IRS_STORE: { 192 | IR_Statement_Store *store = (IR_Statement_Store *)statement; 193 | printf("unimplemented printf IRS_STORE"); 194 | // printf("IRS_STORE 0x%p r%d", stmt->var, stmt->value->reg); 195 | break; 196 | } 197 | case IRS_LOAD: { 198 | IR_Statement_Load *load = (IR_Statement_Load *)statement; 199 | printf("unimplemented printf IRS_LOAD"); 200 | // printf("IRS_LOAD_FROM_VAR r%d 0x%p", stmt->result->reg, stmt->var); 201 | break; 202 | } 203 | default: { 204 | printf("unhandled ir statement kind in print: %d\n", statement->kind); 205 | assert(false); 206 | } 207 | } 208 | } -------------------------------------------------------------------------------- /src/ir.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "parser.h" 4 | #include "checker.h" 5 | 6 | struct IR_Statement; 7 | struct IR_Temporary; 8 | 9 | struct IR_Proc { 10 | Array parameters = {}; 11 | Array all_variables = {}; // parameters + local_variables 12 | Array statements = {}; 13 | IR_Proc() 14 | { 15 | parameters.allocator = g_global_linear_allocator; 16 | all_variables.allocator = g_global_linear_allocator; 17 | statements.allocator = g_global_linear_allocator; 18 | } 19 | }; 20 | 21 | struct IR { 22 | IR_Proc *current_proc = {}; 23 | }; 24 | 25 | 26 | 27 | enum IR_Storage_Kind { 28 | IR_STORAGE_INVALID, 29 | IR_STORAGE_STACK, 30 | IR_STORAGE_GLOBAL, 31 | IR_STORAGE_INDIRECT, 32 | IR_STORAGE_REGISTER, 33 | 34 | IR_STORAGE_COUNT, 35 | }; 36 | 37 | struct IR_Storage { 38 | IR_Storage_Kind kind = {}; 39 | Type *type = {}; 40 | IR_Storage(IR_Storage_Kind kind, Type *type) 41 | : kind(kind) 42 | , type(type) 43 | {} 44 | }; 45 | 46 | struct IR_Stack_Storage : public IR_Storage { 47 | IR_Proc *procedure = {}; 48 | IR_Stack_Storage(IR_Proc *procedure, Type *type) 49 | : IR_Storage(IR_STORAGE_STACK, type) 50 | , procedure(procedure) 51 | {} 52 | }; 53 | 54 | struct IR_Global_Storage : public IR_Storage { 55 | IR_Global_Storage(Type *type) 56 | : IR_Storage(IR_STORAGE_GLOBAL, type) 57 | {} 58 | }; 59 | 60 | struct IR_Indirect_Storage : public IR_Storage { 61 | IR_Storage *points_to = {}; 62 | IR_Indirect_Storage(IR_Storage *points_to, Type *type) 63 | : IR_Storage(IR_STORAGE_INDIRECT, type) 64 | , points_to(points_to) 65 | {} 66 | }; 67 | 68 | struct IR_Register_Storage : public IR_Storage { 69 | IR_Temporary *temporary = {}; 70 | IR_Register_Storage(IR_Temporary *temporary, Type *type) 71 | : IR_Storage(IR_STORAGE_REGISTER, type) 72 | , temporary(temporary) 73 | {} 74 | }; 75 | 76 | 77 | 78 | struct IR_Var { 79 | IR_Storage *storage = {}; 80 | Type *type = {}; 81 | }; 82 | 83 | struct IR_Temporary { 84 | int reg = {}; 85 | }; 86 | 87 | enum IR_Statement_Kind { 88 | IRS_INVALID, 89 | IRS_BINARY, 90 | IRS_MOVE_CONSTANT, 91 | IRS_STORE, 92 | IRS_LOAD, 93 | IRS_INDIRECTION, 94 | 95 | IRS_COUNT, 96 | }; 97 | 98 | struct IR_Statement { 99 | IR_Statement_Kind kind = {}; 100 | IR_Statement(IR_Statement_Kind kind) 101 | : kind(kind) 102 | {} 103 | }; 104 | 105 | enum IR_Op { 106 | IRO_INVALID, 107 | IRO_ADD, 108 | IRO_SUB, 109 | IRO_MUL, 110 | IRO_DIV, 111 | 112 | IRO_COUNT, 113 | }; 114 | 115 | struct IR_Statement_Binary : public IR_Statement { 116 | IR_Storage *lhs = {}; 117 | IR_Storage *rhs = {}; 118 | IR_Register_Storage *result = {}; 119 | IR_Op op = {}; 120 | IR_Statement_Binary() 121 | : IR_Statement(IRS_BINARY) 122 | {} 123 | }; 124 | 125 | struct IR_Statement_Move_Constant : public IR_Statement { 126 | Operand operand = {}; 127 | IR_Register_Storage *result = {}; 128 | IR_Statement_Move_Constant() 129 | : IR_Statement(IRS_MOVE_CONSTANT) 130 | {} 131 | }; 132 | 133 | struct IR_Statement_Store : public IR_Statement { 134 | IR_Storage *dst = {}; 135 | IR_Storage *src = {}; 136 | IR_Statement_Store() 137 | : IR_Statement(IRS_STORE) 138 | {} 139 | }; 140 | 141 | struct IR_Statement_Load : public IR_Statement { 142 | IR_Storage *dst = {}; 143 | IR_Storage *src = {}; 144 | IR_Statement_Load() 145 | : IR_Statement(IRS_LOAD) 146 | {} 147 | }; 148 | 149 | struct IR_Statement_Indirection : public IR_Statement { 150 | IR_Storage *storage_to_indirect = {}; 151 | IR_Storage *result = {}; 152 | IR_Statement_Indirection() 153 | : IR_Statement(IRS_INDIRECTION) 154 | {} 155 | }; 156 | 157 | IR_Proc *generate_ir_proc(IR *ir, Ast_Proc *procedure); 158 | void printf_ir_statement(IR_Statement *statement); -------------------------------------------------------------------------------- /src/lexer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "basic.h" 4 | 5 | struct Location { 6 | const char *filepath = nullptr; 7 | char *text = nullptr; 8 | int index = 0; 9 | int line = 0; 10 | int character = 0; 11 | }; 12 | 13 | enum Token_Kind { 14 | TK_INVALID, 15 | TK_IDENTIFIER, 16 | TK_NUMBER, 17 | TK_STRING, 18 | TK_CHAR, 19 | 20 | TK_CONST, 21 | TK_PROC, 22 | TK_OPERATOR, 23 | TK_STRUCT, 24 | TK_UNION, 25 | TK_ENUM, 26 | TK_RETURN, 27 | TK_NULL, 28 | TK_TRUE, 29 | TK_FALSE, 30 | TK_CAST, 31 | TK_TRANSMUTE, 32 | TK_USING, 33 | TK_DEFER, 34 | 35 | TK_NOTE, 36 | 37 | TK_IF, 38 | TK_ELSE, 39 | TK_FOR, 40 | TK_WHILE, 41 | TK_BREAK, 42 | TK_CONTINUE, 43 | 44 | TK_SIZEOF, 45 | TK_TYPEOF, 46 | TK_TYPEOFELEMENT, 47 | 48 | TK_DIRECTIVE_PRINT, 49 | TK_DIRECTIVE_ASSERT, 50 | TK_DIRECTIVE_FOREIGN, 51 | TK_DIRECTIVE_INCLUDE, 52 | TK_DIRECTIVE_FOREIGN_IMPORT, 53 | TK_DIRECTIVE_FOREIGN_SYSTEM_IMPORT, 54 | TK_DIRECTIVE_PARTIAL, 55 | TK_DIRECTIVE_C_VARARGS, 56 | TK_DIRECTIVE_CALLER_LOCATION, 57 | 58 | TK_ASSIGN, 59 | 60 | TK_PLUS, 61 | TK_PLUS_ASSIGN, 62 | TK_MINUS, 63 | TK_MINUS_ASSIGN, 64 | TK_MULTIPLY, 65 | TK_MULTIPLY_ASSIGN, 66 | TK_DIVIDE, 67 | TK_DIVIDE_ASSIGN, 68 | TK_MOD, 69 | TK_MOD_ASSIGN, 70 | TK_LEFT_SHIFT, 71 | TK_LEFT_SHIFT_ASSIGN, 72 | TK_RIGHT_SHIFT, 73 | TK_RIGHT_SHIFT_ASSIGN, 74 | 75 | TK_DOLLAR, 76 | 77 | TK_AMPERSAND, // bitwise AND and address-of 78 | 79 | TK_QUESTION_MARK, 80 | 81 | TK_BIT_AND_ASSIGN, 82 | TK_BIT_OR, 83 | TK_BIT_OR_ASSIGN, 84 | TK_TILDE, // serves double-duty as unary bitwise NOT and binary bitwise XOR 85 | TK_BIT_XOR_ASSIGN, 86 | 87 | TK_NOT, 88 | 89 | TK_EQUAL_TO, 90 | TK_NOT_EQUAL_TO, 91 | TK_LESS_THAN, 92 | TK_LESS_THAN_OR_EQUAL, 93 | TK_GREATER_THAN, 94 | TK_GREATER_THAN_OR_EQUAL, 95 | 96 | TK_BOOLEAN_AND, 97 | TK_BOOLEAN_AND_ASSIGN, 98 | TK_BOOLEAN_OR, 99 | TK_BOOLEAN_OR_ASSIGN, 100 | 101 | TK_LEFT_CURLY, 102 | TK_RIGHT_CURLY, 103 | TK_LEFT_SQUARE, 104 | TK_RIGHT_SQUARE, 105 | TK_LEFT_PAREN, 106 | TK_RIGHT_PAREN, 107 | 108 | TK_SEMICOLON, 109 | TK_COLON, 110 | TK_DOT, 111 | TK_DOT_DOT, 112 | TK_COMMA, 113 | TK_CARET, 114 | TK_COMPOUND_LITERAL, // note(josh): '.{'' 115 | 116 | TK_COMMENT, 117 | 118 | TK_COUNT, 119 | }; 120 | 121 | struct Token { 122 | char *text = nullptr; 123 | char *escaped_text = nullptr; 124 | Token_Kind kind = TK_INVALID; 125 | Location location = {}; 126 | bool has_a_dot = false; 127 | int escaped_length = {}; 128 | int scanner_length = {}; 129 | i64 int_value = {}; 130 | u64 uint_value = {}; 131 | f32 f32_value = {}; 132 | f64 f64_value = {}; 133 | char char_value = {}; 134 | }; 135 | 136 | struct Ast_Block; 137 | struct Ast_Proc_Header; 138 | 139 | struct Declaration; 140 | 141 | struct Lexer { 142 | char *text = nullptr; 143 | Location location = {}; 144 | bool errored = false; 145 | bool has_peeked_token = {}; 146 | Location peeked_location = {}; 147 | Token peeked_token = {}; 148 | Ast_Block *current_block = {}; 149 | Declaration *current_toplevel_declaration = {}; 150 | Ast_Proc_Header *currently_parsing_proc_body = {}; 151 | Allocator allocator = {}; 152 | int num_polymorphic_variables_parsed = 0; 153 | Lexer(const char *filepath, char *text) 154 | : text(text) 155 | { 156 | location.filepath = filepath; 157 | location.line = 1; 158 | location.character = 1; 159 | } 160 | 161 | Lexer(const char *filepath) 162 | { 163 | location.filepath = filepath; 164 | location.line = 1; 165 | location.character = 1; 166 | } 167 | }; 168 | 169 | void init_lexer_globals(); 170 | 171 | bool get_next_token(Lexer *lexer, Token *out_token); 172 | void eat_next_token(Lexer *lexer, Token *out_token = nullptr); 173 | bool peek_next_token(Lexer *lexer, Token *out_token); 174 | void unexpected_token(Lexer *lexer, Token token, Token_Kind expected = TK_INVALID); 175 | bool expect_token(Lexer *lexer, Token_Kind kind, Token *out_token = nullptr); 176 | void print_token(Token token); 177 | char *token_string(Token_Kind kind); 178 | char *token_name(Token_Kind kind); 179 | 180 | void report_error(Location location, const char *fmt, ...); 181 | void report_info(Location location, const char *fmt, ...); 182 | 183 | #define UNIMPLEMENTED(val) assert(false && "Unimplemented case: " #val "\n"); 184 | 185 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | #include "os_windows.h" 7 | #include "lexer.h" 8 | #include "parser.h" 9 | #include "checker.h" 10 | #include "c_backend.h" 11 | #include "ir.h" 12 | 13 | /* 14 | TODO: 15 | 16 | HIGH PRIORITY 17 | -default procedure parameters? 18 | -#caller_location 19 | -handle usage before declaration 20 | -handle unary operators nicer, currently quick-and-dirty 21 | -#print and #assert don't work in global scope right now 22 | -allow operator overloads to be declared outside a struct 23 | -+=, -=, etc for operator overloading 24 | -do proper nested selector expression elimination with 'using' 25 | -#link_name 26 | -new_slice/new_string are not the right word. use 'make' 27 | -namespacing bug { 28 | proc main() { 29 | bar: int; 30 | foo(); 31 | proc foo() { 32 | printa(bar); // the compiler thinks it knows what 'bar' is, resulting in an error in the C code 33 | } 34 | } 35 | } 36 | -get rid of spinlocking in dynamic_arena, just give each thread its own allocator 37 | -special sauce for [] overloads instead of actually having reference types in the language 38 | 39 | MEDIUM PRIORITY 40 | -add more comprehensive numbers test 41 | -add binary literals 0b0010110111100001 42 | -enum arrays @EnumArrays 43 | -don't generate short-circuiting jumps if it is unnecessary 44 | -put struct constants in Type_Info 45 | -add type_untyped_number that can convert to int or float so that type_untyped_int can't convert to float in the case of a char literal 46 | -deduplicate #foreign_import/#foreign_system_import 47 | -add assert to c_backend that makes sure all declarations have been checked. looks like some array type decls are bypassing check_declaration 48 | -block comments 49 | -while foo := bar(); (foo != null) 50 | -sized bool types 51 | -underscores in numbers 52 | -uninitialized stack members 53 | -error if you use reserved C identifiers like 'char', 'double', 'signed', etc? 54 | -Expr_Change (v2 := v1.{y=4};) 55 | -slicing 56 | -declare distinct types 57 | -allow passing a ^Derived to a proc that takes ^Base if the first field in Derived is 'using base: Base' 58 | -procedure literal expression handling 59 | -#no_bounds_checks 60 | -#no_null_checks 61 | -remove runtime's dependency on basic 62 | -@UnaryOperatorOverloading 63 | -switch statements 64 | -ternary operator maybe? 65 | -read command line parameters 66 | -good logging of cyclic dependencies 67 | -reference-to-reference parsing bug: var x: >>int lexes as a shift-right 68 | -what should the semantics be for reference-to-reference? 69 | -foreach loops 70 | -#if 71 | -#location() 72 | -multiple return values 73 | -namespaced imports i.e. `#include Foo "foo.sif"` 74 | -append(&dyn, {1, 4, 9}); doesn't work because we can't use expected types when initially checking a polymorphic procedure right now 75 | 76 | LOW PRIORITY 77 | -utf8 strings 78 | -enum field @notes? 79 | -#this to refer to struct type for anonymous 80 | -use a custom allocator in __init_sif_runtime 81 | -add allocators to demo 82 | -opt=N 83 | -cast(Type)value syntax in addition to the current cast(Type, value) maybe? 84 | -unknown directives don't stop compilation 85 | -figure out if I should allow shadowing (maybe with a keyword?) 86 | -build to dll 87 | -check for use-before-declaration of local vars 88 | -make C output a bit prettier, whatever that means 89 | -tagged unions? 90 | -allow custom entrypoints 91 | -implicit polymorphism 92 | -loop/block labels a la Odin 93 | -<<< and >>> for shift rotate? 94 | -procedure overloading 95 | */ 96 | 97 | void print_usage() { 98 | printf("sif compiler v0.2.1\n\n"); 99 | 100 | printf("Usage:\n"); 101 | printf(" sif [optional flags]\n\n"); 102 | 103 | printf("Optional flags:\n"); 104 | printf(" -o .exe Override the default output .exe name.\n"); 105 | // printf(" -opt Optimization level.\n"); 106 | printf(" -debug Generate debug information.\n"); 107 | printf(" -log-cl-command Print the invocation command for cl.exe.\n"); 108 | printf(" -show-timings Print a log of times for different compilation stages.\n"); 109 | printf(" -keep-temp-files Don't delete intermediate files used for compilation.\n"); 110 | printf(" -no-type-info Don't generate runtime type information.\n"); 111 | printf(" -no-threads Don't compile with multithreading.\n"); 112 | } 113 | 114 | char *sif_core_lib_path; 115 | 116 | Allocator g_global_linear_allocator; 117 | 118 | bool g_logged_error = {}; 119 | 120 | Timer g_global_timer = {}; 121 | 122 | // todo(josh): put these in some kind of Build_Options struct in common.h? 123 | bool g_no_type_info = {}; 124 | bool g_no_threads = {}; 125 | bool g_is_debug_build = {}; 126 | 127 | int main(int argc, char **argv) { 128 | init_timer(&g_global_timer); 129 | double application_start_time = query_timer(&g_global_timer); 130 | 131 | if (argc < 3) { 132 | print_usage(); 133 | return 0; 134 | } 135 | 136 | Dynamic_Arena dynamic_arena = {}; 137 | init_dynamic_arena(&dynamic_arena, 25 * 1024 * 1024, default_allocator()); 138 | g_global_linear_allocator = dynamic_arena_allocator(&dynamic_arena); 139 | 140 | char *sif_exe_path = get_current_exe_name(g_global_linear_allocator); 141 | char *sif_root = path_directory(sif_exe_path, g_global_linear_allocator); 142 | String_Builder core_lib_builder = make_string_builder(g_global_linear_allocator, 64); 143 | core_lib_builder.printf("%s/core", sif_root); 144 | sif_core_lib_path = core_lib_builder.string(); 145 | 146 | bool is_run = false; 147 | bool is_build = false; 148 | bool is_check = false; 149 | 150 | if (strcmp(argv[1], "run") == 0) { 151 | is_run = true; 152 | } 153 | else if (strcmp(argv[1], "build") == 0) { 154 | is_build = true; 155 | } 156 | else if (strcmp(argv[1], "check") == 0) { 157 | is_check = true; 158 | } 159 | else { 160 | printf("Unknown compiler flag: %s\n", argv[1]); 161 | print_usage(); 162 | return 0; 163 | } 164 | 165 | char *file_to_compile = argv[2]; 166 | if (!ends_with(file_to_compile, ".sif")) { 167 | printf("File to compile must end with '.sif'"); 168 | return 0; 169 | } 170 | 171 | bool show_timings = false; 172 | bool keep_temp_files = false; 173 | bool log_cl_command = false; 174 | bool ir_test = false; 175 | 176 | char *output_exe_name = nullptr; 177 | 178 | for (int i = 3; i < argc; i++) { 179 | char *arg = argv[i]; 180 | if (strcmp(arg, "-show-timings") == 0) { 181 | show_timings = true; 182 | } 183 | else if (strcmp(arg, "-keep-temp-files") == 0) { 184 | keep_temp_files = true; 185 | } 186 | else if (strcmp(arg, "-debug") == 0) { 187 | g_is_debug_build = true; 188 | } 189 | else if (strcmp(arg, "-no-type-info") == 0) { 190 | g_no_type_info = true; 191 | } 192 | else if (strcmp(arg, "-no-threads") == 0) { 193 | g_no_threads = true; 194 | } 195 | else if (strcmp(arg, "-log-cl-command") == 0) { 196 | log_cl_command = true; 197 | } 198 | else if (strcmp(arg, "-ir-test") == 0) { 199 | ir_test = true; 200 | } 201 | else if (strcmp(arg, "-o") == 0) { 202 | if ((i+1) >= argc) { 203 | printf("Missing argument for -o flag."); 204 | return 0; 205 | } 206 | if (!ends_with(argv[i+1], ".exe")) { 207 | printf("Argument for -o must end with '.exe'"); 208 | return 0; 209 | } 210 | output_exe_name = argv[i+1]; 211 | i += 1; 212 | } 213 | else { 214 | printf("Unknown compiler flag: %s\n", arg); 215 | print_usage(); 216 | return 0; 217 | } 218 | } 219 | 220 | if (output_exe_name == nullptr) { 221 | char *file_to_compile_without_extension = path_filename(file_to_compile, g_global_linear_allocator); 222 | String_Builder output_exe_name_sb = make_string_builder(g_global_linear_allocator, 64); 223 | output_exe_name_sb.printf("%s.exe", file_to_compile_without_extension); 224 | output_exe_name = output_exe_name_sb.string(); 225 | } 226 | 227 | init_lexer_globals(); 228 | init_interned_strings(); 229 | init_parser(); 230 | init_checker(); 231 | 232 | double parsing_start_time = query_timer(&g_global_timer); 233 | 234 | Ast_Block *global_scope = begin_parsing(file_to_compile); 235 | if (!global_scope || g_logged_error) { 236 | printf("There were errors.\n"); 237 | return 0; 238 | } 239 | 240 | add_global_declarations(global_scope); 241 | 242 | double checking_start_time = query_timer(&g_global_timer); 243 | 244 | init_c_backend(); 245 | 246 | bool check_success = typecheck_global_scope(global_scope); 247 | if (!check_success) { 248 | printf("There were errors.\n"); 249 | return 0; 250 | } 251 | 252 | if (ir_test) { 253 | IR *ir = SIF_NEW_CLONE(IR(), g_global_linear_allocator); 254 | IR_Proc *ir_main = generate_ir_proc(ir, g_main_proc); 255 | For (idx, ir_main->statements) { 256 | IR_Statement *statement = ir_main->statements[idx]; 257 | printf_ir_statement(statement); 258 | printf("\n"); 259 | } 260 | } 261 | 262 | double codegen_start_time = query_timer(&g_global_timer); 263 | 264 | Chunked_String_Builder c_code = generate_c_main_file(global_scope); 265 | write_entire_file("output.c", c_code.make_string()); 266 | 267 | double c_compile_start_time = query_timer(&g_global_timer); 268 | 269 | if (!is_check) { 270 | String_Builder command_sb = make_string_builder(g_global_linear_allocator, 128); 271 | command_sb.printf("cmd.exe /O2 /c \"cl.exe "); 272 | if (g_is_debug_build) { 273 | command_sb.print("/Zi /Fd "); 274 | } 275 | command_sb.print("output.c /nologo /wd4028 "); 276 | 277 | For (idx, g_all_foreign_import_directives) { 278 | command_sb.printf("\"%s\" ", g_all_foreign_import_directives[idx]->path); 279 | } 280 | command_sb.printf("/link /OUT:\"%s\" ", output_exe_name); 281 | if (g_is_debug_build) { 282 | command_sb.print("/DEBUG "); 283 | } 284 | command_sb.printf("\""); 285 | 286 | if (log_cl_command) { 287 | printf("%s\n", command_sb.string()); 288 | } 289 | 290 | FILE *pipe = _popen(command_sb.string(), "r"); 291 | char buffer[1024]; 292 | Chunked_String_Builder error_sb = make_chunked_string_builder(g_global_linear_allocator, 1024); 293 | bool first_thing = true; 294 | while (fgets(buffer, sizeof(buffer), pipe)) { 295 | char *str = buffer; 296 | if (first_thing) { 297 | if (starts_with(buffer, "output.c\n")) { 298 | str += strlen("output.c\n"); 299 | } 300 | } 301 | error_sb.print(str); 302 | first_thing = false; 303 | } 304 | char *errors = error_sb.make_string(); 305 | printf(errors); 306 | if (strlen(errors) != 0) { 307 | printf("\nInternal compiler error: sif encountered an error when compiling C output. Exiting.\n"); 308 | return 0; 309 | } 310 | 311 | if (!keep_temp_files) { 312 | delete_file("output.c"); 313 | delete_file("output.obj"); 314 | } 315 | } 316 | 317 | double compilation_end_time = query_timer(&g_global_timer); 318 | 319 | if (show_timings) { 320 | float setup_time = parsing_start_time - application_start_time; 321 | float parse_time = checking_start_time - parsing_start_time; 322 | float check_time = codegen_start_time - checking_start_time; 323 | float codegen_time = c_compile_start_time - codegen_start_time; 324 | float cl_time = compilation_end_time - c_compile_start_time; 325 | float sif_time = c_compile_start_time - application_start_time; 326 | float total_time = compilation_end_time - application_start_time; 327 | 328 | printf("-----------------------------\n"); 329 | printf("| sif compile timings |\n"); 330 | printf("-----------------------------\n"); 331 | printf(" Setup time: %.3fms (%.2f%% sif, %.2f%% total)\n", setup_time, (setup_time / sif_time * 100), (setup_time / total_time * 100)); 332 | printf(" Parse time: %.3fms (%.2f%% sif, %.2f%% total)\n", parse_time, (parse_time / sif_time * 100), (parse_time / total_time * 100)); 333 | printf(" Check time: %.3fms (%.2f%% sif, %.2f%% total)\n", check_time, (check_time / sif_time * 100), (check_time / total_time * 100)); 334 | printf("Codegen time: %.3fms (%.2f%% sif, %.2f%% total)\n", codegen_time, (codegen_time / sif_time * 100), (codegen_time / total_time * 100)); 335 | printf(" cl.exe time: %.3fms (%.2f%% sif, %.2f%% total)\n", cl_time, (cl_time / sif_time * 100), (cl_time / total_time * 100)); 336 | printf("\n"); 337 | printf(" Sif time: %fms\n", sif_time); 338 | printf("Total time: %fms\n", total_time); 339 | printf("\n"); 340 | printf(" Total lines: %d\n", g_total_lines_parsed); 341 | printf("Sif lines/sec: %.0f\n", ((float)g_total_lines_parsed) / (sif_time / 1000)); 342 | } 343 | 344 | if (is_run) { 345 | String_Builder run_command_sb = make_string_builder(g_global_linear_allocator, 64); 346 | run_command_sb.printf("cmd.exe /c \"%s\"", output_exe_name); 347 | system(run_command_sb.string()); 348 | } 349 | } -------------------------------------------------------------------------------- /src/microsoft_craziness.h: -------------------------------------------------------------------------------- 1 | // 2 | // Author: Jonathan Blow 3 | // Version: 1 4 | // Date: 31 August, 2018 5 | // 6 | // This code is released under the MIT license, which you can find at 7 | // 8 | // https://opensource.org/licenses/MIT 9 | // 10 | // 11 | // 12 | // See the comments for how to use this library just below the includes. 13 | // 14 | 15 | 16 | // 17 | // NOTE(Kalinovcic): I have translated the original implementation to C, 18 | // and made a preprocessor define that enables people to include it without 19 | // the implementation, just as a header. 20 | // 21 | // I also fixed two bugs: 22 | // - If COM initialization for VS2017 fails, we actually properly continue 23 | // searching for earlier versions in the registry. 24 | // - For earlier versions, the code now returns the "bin\amd64" VS directory. 25 | // Previously it was returning the "bin" directory, which is for x86 stuff. 26 | // 27 | // To include the implementation, before including microsoft_craziness.h, define: 28 | // 29 | // #define MICROSOFT_CRAZINESS_IMPLEMENTATION 30 | // #include "microsoft_craziness.h" 31 | // 32 | 33 | #ifdef __cplusplus 34 | extern "C" 35 | { 36 | #endif 37 | 38 | #ifndef MICROSOFT_CRAZINESS_HEADER_GUARD 39 | #define MICROSOFT_CRAZINESS_HEADER_GUARD 40 | 41 | #include 42 | 43 | typedef struct 44 | { 45 | int windows_sdk_version; // Zero if no Windows SDK found. 46 | 47 | wchar_t *windows_sdk_root; 48 | wchar_t *windows_sdk_um_library_path; 49 | wchar_t *windows_sdk_ucrt_library_path; 50 | 51 | wchar_t *vs_exe_path; 52 | wchar_t *vs_library_path; 53 | } Find_Result; 54 | 55 | Find_Result find_visual_studio_and_windows_sdk(); 56 | void free_resources(Find_Result *result); 57 | 58 | #endif // MICROSOFT_CRAZINESS_HEADER_GUARD 59 | 60 | 61 | // 62 | // HOW TO USE THIS CODE 63 | // 64 | // The purpose of this file is to find the folders that contain libraries 65 | // you may need to link against, on Windows, if you are linking with any 66 | // compiled C or C++ code. This will be necessary for many non-C++ programming 67 | // language environments that want to provide compatibility. 68 | // 69 | // We find the place where the Visual Studio libraries live (for example, 70 | // libvcruntime.lib), where the linker and compiler executables live 71 | // (for example, link.exe), and where the Windows SDK libraries reside 72 | // (kernel32.lib, libucrt.lib). 73 | // 74 | // We all wish you didn't have to worry about so many weird dependencies, 75 | // but we don't really have a choice about this, sadly. 76 | // 77 | // I don't claim that this is the absolute best way to solve this problem, 78 | // and so far we punt on things (if you have multiple versions of Visual Studio 79 | // installed, we return the first one, rather than the newest). But it 80 | // will solve the basic problem for you as simply as I know how to do it, 81 | // and because there isn't too much code here, it's easy to modify and expand. 82 | // 83 | // 84 | // Here is the API you need to know about: 85 | // 86 | 87 | 88 | // 89 | // Call find_visual_studio_and_windows_sdk, look at the resulting 90 | // paths, then call free_resources on the result. 91 | // 92 | // Everything else in this file is implementation details that you 93 | // don't need to care about. 94 | // 95 | 96 | // 97 | // This file was about 400 lines before we started adding these comments. 98 | // You might think that's way too much code to do something as simple 99 | // as finding a few library and executable paths. I agree. However, 100 | // Microsoft's own solution to this problem, called "vswhere", is a 101 | // mere EIGHT THOUSAND LINE PROGRAM, spread across 70 files, 102 | // that they posted to github *unironically*. 103 | // 104 | // I am not making this up: https://github.com/Microsoft/vswhere 105 | // 106 | // Several people have therefore found the need to solve this problem 107 | // themselves. We referred to some of these other solutions when 108 | // figuring out what to do, most prominently ziglang's version, 109 | // by Ryan Saunderson. 110 | // 111 | // I hate this kind of code. The fact that we have to do this at all 112 | // is stupid, and the actual maneuvers we need to go through 113 | // are just painful. If programming were like this all the time, 114 | // I would quit. 115 | // 116 | // Because this is such an absurd waste of time, I felt it would be 117 | // useful to package the code in an easily-reusable way, in the 118 | // style of the stb libraries. We haven't gone as all-out as some 119 | // of the stb libraries do (which compile in C with no includes, often). 120 | // For this version you need C++ and the headers at the top of the file. 121 | // 122 | // We return the strings as Windows wide character strings. Aesthetically 123 | // I don't like that (I think most sane programs are UTF-8 internally), 124 | // but apparently, not all valid Windows file paths can even be converted 125 | // correctly to UTF-8. So have fun with that. It felt safest and simplest 126 | // to stay with wchar_t since all of this code is fully ensconced in 127 | // Windows crazy-land. 128 | // 129 | // One other shortcut I took is that this is hardcoded to return the 130 | // folders for x64 libraries. If you want x86 or arm, you can make 131 | // slight edits to the code below, or, if enough people want this, 132 | // I can work it in here. 133 | // 134 | 135 | 136 | 137 | #ifdef MICROSOFT_CRAZINESS_IMPLEMENTATION 138 | #ifndef MICROSOFT_CRAZINESS_IMPLEMENTATION_GUARD 139 | #define MICROSOFT_CRAZINESS_IMPLEMENTATION_GUARD 140 | 141 | 142 | #include 143 | #include 144 | #include 145 | #include 146 | #include // For _get_osfhandle 147 | 148 | 149 | void free_resources(Find_Result *result) { 150 | free(result->windows_sdk_root); 151 | free(result->windows_sdk_um_library_path); 152 | free(result->windows_sdk_ucrt_library_path); 153 | free(result->vs_exe_path); 154 | free(result->vs_library_path); 155 | } 156 | 157 | 158 | // COM objects for the ridiculous Microsoft craziness. 159 | 160 | #undef INTERFACE 161 | #define INTERFACE ISetupInstance 162 | DECLARE_INTERFACE_ (ISetupInstance, IUnknown) 163 | { 164 | BEGIN_INTERFACE 165 | 166 | // IUnknown methods 167 | STDMETHOD (QueryInterface) (THIS_ REFIID, void **) PURE; 168 | STDMETHOD_(ULONG, AddRef) (THIS) PURE; 169 | STDMETHOD_(ULONG, Release) (THIS) PURE; 170 | 171 | // ISetupInstance methods 172 | STDMETHOD(GetInstanceId)(THIS_ _Out_ BSTR* pbstrInstanceId) PURE; 173 | STDMETHOD(GetInstallDate)(THIS_ _Out_ LPFILETIME pInstallDate) PURE; 174 | STDMETHOD(GetInstallationName)(THIS_ _Out_ BSTR* pbstrInstallationName) PURE; 175 | STDMETHOD(GetInstallationPath)(THIS_ _Out_ BSTR* pbstrInstallationPath) PURE; 176 | STDMETHOD(GetInstallationVersion)(THIS_ _Out_ BSTR* pbstrInstallationVersion) PURE; 177 | STDMETHOD(GetDisplayName)(THIS_ _In_ LCID lcid, _Out_ BSTR* pbstrDisplayName) PURE; 178 | STDMETHOD(GetDescription)(THIS_ _In_ LCID lcid, _Out_ BSTR* pbstrDescription) PURE; 179 | STDMETHOD(ResolvePath)(THIS_ _In_opt_z_ LPCOLESTR pwszRelativePath, _Out_ BSTR* pbstrAbsolutePath) PURE; 180 | 181 | END_INTERFACE 182 | }; 183 | 184 | #undef INTERFACE 185 | #define INTERFACE IEnumSetupInstances 186 | DECLARE_INTERFACE_ (IEnumSetupInstances, IUnknown) 187 | { 188 | BEGIN_INTERFACE 189 | 190 | // IUnknown methods 191 | STDMETHOD (QueryInterface) (THIS_ REFIID, void **) PURE; 192 | STDMETHOD_(ULONG, AddRef) (THIS) PURE; 193 | STDMETHOD_(ULONG, Release) (THIS) PURE; 194 | 195 | // IEnumSetupInstances methods 196 | STDMETHOD(Next)(THIS_ _In_ ULONG celt, _Out_writes_to_(celt, *pceltFetched) ISetupInstance** rgelt, _Out_opt_ _Deref_out_range_(0, celt) ULONG* pceltFetched) PURE; 197 | STDMETHOD(Skip)(THIS_ _In_ ULONG celt) PURE; 198 | STDMETHOD(Reset)(THIS) PURE; 199 | STDMETHOD(Clone)(THIS_ _Deref_out_opt_ IEnumSetupInstances** ppenum) PURE; 200 | 201 | END_INTERFACE 202 | }; 203 | 204 | #undef INTERFACE 205 | #define INTERFACE ISetupConfiguration 206 | DECLARE_INTERFACE_ (ISetupConfiguration, IUnknown) 207 | { 208 | BEGIN_INTERFACE 209 | 210 | // IUnknown methods 211 | STDMETHOD (QueryInterface) (THIS_ REFIID, void **) PURE; 212 | STDMETHOD_(ULONG, AddRef) (THIS) PURE; 213 | STDMETHOD_(ULONG, Release) (THIS) PURE; 214 | 215 | // ISetupConfiguration methods 216 | STDMETHOD(EnumInstances)(THIS_ _Out_ IEnumSetupInstances** ppEnumInstances) PURE; 217 | STDMETHOD(GetInstanceForCurrentProcess)(THIS_ _Out_ ISetupInstance** ppInstance) PURE; 218 | STDMETHOD(GetInstanceForPath)(THIS_ _In_z_ LPCWSTR wzPath, _Out_ ISetupInstance** ppInstance) PURE; 219 | 220 | END_INTERFACE 221 | }; 222 | 223 | #ifdef __cplusplus 224 | #define CALL_STDMETHOD(object, method, ...) object->method(__VA_ARGS__) 225 | #define CALL_STDMETHOD_(object, method) object->method() 226 | #else 227 | #define CALL_STDMETHOD(object, method, ...) object->lpVtbl->method(object, __VA_ARGS__) 228 | #define CALL_STDMETHOD_(object, method) object->lpVtbl->method(object) 229 | #endif 230 | 231 | 232 | // The beginning of the actual code that does things. 233 | 234 | typedef struct { 235 | int32_t best_version[4]; // For Windows 8 versions, only two of these numbers are used. 236 | wchar_t *best_name; 237 | } Version_Data; 238 | 239 | bool os_file_exists(wchar_t *name) { 240 | // @Robustness: What flags do we really want to check here? 241 | 242 | auto attrib = GetFileAttributesW(name); 243 | if (attrib == INVALID_FILE_ATTRIBUTES) return false; 244 | if (attrib & FILE_ATTRIBUTE_DIRECTORY) return false; 245 | 246 | return true; 247 | } 248 | 249 | #define concat2(a, b) concat(a, b, NULL, NULL) 250 | #define concat3(a, b, c) concat(a, b, c, NULL) 251 | #define concat4(a, b, c, d) concat(a, b, c, d) 252 | wchar_t *concat(wchar_t *a, wchar_t *b, wchar_t *c, wchar_t *d) { 253 | // Concatenate up to 4 wide strings together. Allocated with malloc. 254 | // If you don't like that, use a programming language that actually 255 | // helps you with using custom allocators. Or just edit the code. 256 | 257 | auto len_a = wcslen(a); 258 | auto len_b = wcslen(b); 259 | 260 | auto len_c = 0; 261 | if (c) len_c = wcslen(c); 262 | 263 | auto len_d = 0; 264 | if (d) len_d = wcslen(d); 265 | 266 | wchar_t *result = (wchar_t *)malloc((len_a + len_b + len_c + len_d + 1) * 2); 267 | memcpy(result, a, len_a*2); 268 | memcpy(result + len_a, b, len_b*2); 269 | 270 | if (c) memcpy(result + len_a + len_b, c, len_c * 2); 271 | if (d) memcpy(result + len_a + len_b + len_c, d, len_d * 2); 272 | 273 | result[len_a + len_b + len_c + len_d] = 0; 274 | 275 | return result; 276 | } 277 | 278 | typedef void (*Visit_Proc_W)(wchar_t *short_name, wchar_t *full_name, Version_Data *data); 279 | bool visit_files_w(wchar_t *dir_name, Version_Data *data, Visit_Proc_W proc) { 280 | 281 | // Visit everything in one folder (non-recursively). If it's a directory 282 | // that doesn't start with ".", call the visit proc on it. The visit proc 283 | // will see if the filename conforms to the expected versioning pattern. 284 | 285 | WIN32_FIND_DATAW find_data; 286 | 287 | wchar_t *wildcard_name = concat2(dir_name, L"\\*"); 288 | HANDLE handle = FindFirstFileW(wildcard_name, &find_data); 289 | free(wildcard_name); 290 | 291 | if (handle == INVALID_HANDLE_VALUE) return false; 292 | 293 | while (true) { 294 | if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (find_data.cFileName[0] != '.')) { 295 | wchar_t *full_name = concat3(dir_name, L"\\", find_data.cFileName); 296 | proc(find_data.cFileName, full_name, data); 297 | free(full_name); 298 | } 299 | 300 | BOOL success = FindNextFileW(handle, &find_data); 301 | if (!success) break; 302 | } 303 | 304 | FindClose(handle); 305 | 306 | return true; 307 | } 308 | 309 | 310 | wchar_t *find_windows_kit_root_with_key(HKEY key, wchar_t *version) { 311 | // Given a key to an already opened registry entry, 312 | // get the value stored under the 'version' subkey. 313 | // If that's not the right terminology, hey, I never do registry stuff. 314 | 315 | DWORD required_length; 316 | auto rc = RegQueryValueExW(key, version, NULL, NULL, NULL, &required_length); 317 | if (rc != 0) return NULL; 318 | 319 | DWORD length = required_length + 2; // The +2 is for the maybe optional zero later on. Probably we are over-allocating. 320 | wchar_t *value = (wchar_t *)malloc(length); 321 | if (!value) return NULL; 322 | 323 | rc = RegQueryValueExW(key, version, NULL, NULL, (LPBYTE)value, &length); // We know that version is zero-terminated... 324 | if (rc != 0) return NULL; 325 | 326 | // The documentation says that if the string for some reason was not stored 327 | // with zero-termination, we need to manually terminate it. Sigh!! 328 | 329 | if (value[length]) { 330 | value[length+1] = 0; 331 | } 332 | 333 | return value; 334 | } 335 | 336 | void win10_best(wchar_t *short_name, wchar_t *full_name, Version_Data *data) { 337 | // Find the Windows 10 subdirectory with the highest version number. 338 | 339 | int i0, i1, i2, i3; 340 | auto success = swscanf_s(short_name, L"%d.%d.%d.%d", &i0, &i1, &i2, &i3); 341 | if (success < 4) return; 342 | 343 | if (i0 < data->best_version[0]) return; 344 | else if (i0 == data->best_version[0]) { 345 | if (i1 < data->best_version[1]) return; 346 | else if (i1 == data->best_version[1]) { 347 | if (i2 < data->best_version[2]) return; 348 | else if (i2 == data->best_version[2]) { 349 | if (i3 < data->best_version[3]) return; 350 | } 351 | } 352 | } 353 | 354 | // we have to copy_string and free here because visit_files free's the full_name string 355 | // after we execute this function, so Win*_Data would contain an invalid pointer. 356 | if (data->best_name) free(data->best_name); 357 | data->best_name = _wcsdup(full_name); 358 | 359 | if (data->best_name) { 360 | data->best_version[0] = i0; 361 | data->best_version[1] = i1; 362 | data->best_version[2] = i2; 363 | data->best_version[3] = i3; 364 | } 365 | } 366 | 367 | void win8_best(wchar_t *short_name, wchar_t *full_name, Version_Data *data) { 368 | // Find the Windows 8 subdirectory with the highest version number. 369 | 370 | int i0, i1; 371 | auto success = swscanf_s(short_name, L"winv%d.%d", &i0, &i1); 372 | if (success < 2) return; 373 | 374 | if (i0 < data->best_version[0]) return; 375 | else if (i0 == data->best_version[0]) { 376 | if (i1 < data->best_version[1]) return; 377 | } 378 | 379 | // we have to copy_string and free here because visit_files free's the full_name string 380 | // after we execute this function, so Win*_Data would contain an invalid pointer. 381 | if (data->best_name) free(data->best_name); 382 | data->best_name = _wcsdup(full_name); 383 | 384 | if (data->best_name) { 385 | data->best_version[0] = i0; 386 | data->best_version[1] = i1; 387 | } 388 | } 389 | 390 | void find_windows_kit_root(Find_Result *result) { 391 | // Information about the Windows 10 and Windows 8 development kits 392 | // is stored in the same place in the registry. We open a key 393 | // to that place, first checking preferntially for a Windows 10 kit, 394 | // then, if that's not found, a Windows 8 kit. 395 | 396 | HKEY main_key; 397 | 398 | LSTATUS rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", 399 | 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &main_key); 400 | if (rc != S_OK) return; 401 | 402 | // Look for a Windows 10 entry. 403 | wchar_t *windows10_root = find_windows_kit_root_with_key(main_key, L"KitsRoot10"); 404 | 405 | if (windows10_root) { 406 | wchar_t *windows10_lib = concat2(windows10_root, L"Lib"); 407 | free(windows10_root); 408 | 409 | Version_Data data = {0}; 410 | visit_files_w(windows10_lib, &data, win10_best); 411 | free(windows10_lib); 412 | 413 | if (data.best_name) { 414 | result->windows_sdk_version = 10; 415 | result->windows_sdk_root = data.best_name; 416 | RegCloseKey(main_key); 417 | return; 418 | } 419 | } 420 | 421 | // Look for a Windows 8 entry. 422 | wchar_t *windows8_root = find_windows_kit_root_with_key(main_key, L"KitsRoot81"); 423 | 424 | if (windows8_root) { 425 | wchar_t *windows8_lib = concat2(windows8_root, L"Lib"); 426 | free(windows8_root); 427 | 428 | Version_Data data = {0}; 429 | visit_files_w(windows8_lib, &data, win8_best); 430 | free(windows8_lib); 431 | 432 | if (data.best_name) { 433 | result->windows_sdk_version = 8; 434 | result->windows_sdk_root = data.best_name; 435 | RegCloseKey(main_key); 436 | return; 437 | } 438 | } 439 | 440 | // If we get here, we failed to find anything. 441 | RegCloseKey(main_key); 442 | } 443 | 444 | bool find_visual_studio_2017_by_fighting_through_microsoft_craziness(Find_Result *result) { 445 | HRESULT rc = CoInitialize(NULL); 446 | // "Subsequent valid calls return false." So ignore false. 447 | // if rc != S_OK return false; 448 | 449 | GUID my_uid = {0x42843719, 0xDB4C, 0x46C2, {0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B}}; 450 | GUID CLSID_SetupConfiguration = {0x177F0C4A, 0x1CD3, 0x4DE7, {0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D}}; 451 | 452 | ISetupConfiguration *config = NULL; 453 | 454 | // NOTE(Kalinovcic): This is so stupid... These functions take references, so the code is different for C and C++...... 455 | #ifdef __cplusplus 456 | HRESULT hr = CoCreateInstance(CLSID_SetupConfiguration, NULL, CLSCTX_INPROC_SERVER, my_uid, (void **)&config); 457 | #else 458 | HRESULT hr = CoCreateInstance(&CLSID_SetupConfiguration, NULL, CLSCTX_INPROC_SERVER, &my_uid, (void **)&config); 459 | #endif 460 | 461 | if (hr != 0) return false; 462 | 463 | IEnumSetupInstances *instances = NULL; 464 | hr = CALL_STDMETHOD(config, EnumInstances, &instances); 465 | CALL_STDMETHOD_(config, Release); 466 | if (hr != 0) return false; 467 | if (!instances) return false; 468 | 469 | bool found_visual_studio_2017 = false; 470 | while (1) { 471 | ULONG found = 0; 472 | ISetupInstance *instance = NULL; 473 | HRESULT hr = CALL_STDMETHOD(instances, Next, 1, &instance, &found); 474 | if (hr != S_OK) break; 475 | 476 | BSTR bstr_inst_path; 477 | hr = CALL_STDMETHOD(instance, GetInstallationPath, &bstr_inst_path); 478 | CALL_STDMETHOD_(instance, Release); 479 | if (hr != S_OK) continue; 480 | 481 | wchar_t *tools_filename = concat2(bstr_inst_path, L"\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); 482 | SysFreeString(bstr_inst_path); 483 | 484 | FILE *f; 485 | errno_t open_result = _wfopen_s(&f, tools_filename, L"rt"); 486 | free(tools_filename); 487 | if (open_result != 0) continue; 488 | if (!f) continue; 489 | 490 | LARGE_INTEGER tools_file_size; 491 | HANDLE file_handle = (HANDLE)_get_osfhandle(_fileno(f)); 492 | BOOL success = GetFileSizeEx(file_handle, &tools_file_size); 493 | if (!success) { 494 | fclose(f); 495 | continue; 496 | } 497 | 498 | uint64_t version_bytes = (tools_file_size.QuadPart + 1) * 2; // Warning: This multiplication by 2 presumes there is no variable-length encoding in the wchars (wacky characters in the file could betray this expectation). 499 | wchar_t *version = (wchar_t *)malloc(version_bytes); 500 | 501 | wchar_t *read_result = fgetws(version, version_bytes, f); 502 | fclose(f); 503 | if (!read_result) continue; 504 | 505 | wchar_t *version_tail = wcschr(version, '\n'); 506 | if (version_tail) *version_tail = 0; // Stomp the data, because nobody cares about it. 507 | 508 | wchar_t *library_path = concat4(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x64"); 509 | wchar_t *library_file = concat2(library_path, L"\\vcruntime.lib"); // @Speed: Could have library_path point to this string, with a smaller count, to save on memory flailing! 510 | 511 | if (os_file_exists(library_file)) { 512 | wchar_t *link_exe_path = concat4(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx64\\x64"); 513 | free(version); 514 | 515 | result->vs_exe_path = link_exe_path; 516 | result->vs_library_path = library_path; 517 | found_visual_studio_2017 = true; 518 | break; 519 | } 520 | 521 | free(version); 522 | 523 | /* 524 | Ryan Saunderson said: 525 | "Clang uses the 'SetupInstance->GetInstallationVersion' / ISetupHelper->ParseVersion to find the newest version 526 | and then reads the tools file to define the tools path - which is definitely better than what i did." 527 | 528 | So... @Incomplete: Should probably pick the newest version... 529 | */ 530 | } 531 | 532 | CALL_STDMETHOD_(instances, Release); 533 | return found_visual_studio_2017; 534 | } 535 | 536 | void find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *result) { 537 | // The name of this procedure is kind of cryptic. Its purpose is 538 | // to fight through Microsoft craziness. The things that the fine 539 | // Visual Studio team want you to do, JUST TO FIND A SINGLE FOLDER 540 | // THAT EVERYONE NEEDS TO FIND, are ridiculous garbage. 541 | 542 | // For earlier versions of Visual Studio, you'd find this information in the registry, 543 | // similarly to the Windows Kits above. But no, now it's the future, so to ask the 544 | // question "Where is the Visual Studio folder?" you have to do a bunch of COM object 545 | // instantiation, enumeration, and querying. (For extra bonus points, try doing this in 546 | // a new, underdeveloped programming language where you don't have COM routines up 547 | // and running yet. So fun.) 548 | // 549 | // If all this COM object instantiation, enumeration, and querying doesn't give us 550 | // a useful result, we drop back to the registry-checking method. 551 | 552 | bool found_visual_studio_2017 = find_visual_studio_2017_by_fighting_through_microsoft_craziness(result); 553 | if (found_visual_studio_2017) return; 554 | 555 | 556 | // If we get here, we didn't find Visual Studio 2017. Try earlier versions. 557 | 558 | HKEY vs7_key; 559 | HRESULT rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &vs7_key); 560 | if (rc != S_OK) return; 561 | 562 | // Hardcoded search for 4 prior Visual Studio versions. Is there something better to do here? 563 | wchar_t *versions[] = { L"14.0", L"12.0", L"11.0", L"10.0" }; 564 | const int NUM_VERSIONS = sizeof(versions) / sizeof(versions[0]); 565 | 566 | for (int i = 0; i < NUM_VERSIONS; i++) { 567 | wchar_t *v = versions[i]; 568 | 569 | DWORD dw_type; 570 | DWORD cb_data; 571 | 572 | LSTATUS rc = RegQueryValueExW(vs7_key, v, NULL, &dw_type, NULL, &cb_data); 573 | if ((rc == ERROR_FILE_NOT_FOUND) || (dw_type != REG_SZ)) { 574 | continue; 575 | } 576 | 577 | wchar_t *buffer = (wchar_t *)malloc(cb_data); 578 | if (!buffer) return; 579 | 580 | rc = RegQueryValueExW(vs7_key, v, NULL, NULL, (LPBYTE)buffer, &cb_data); 581 | if (rc != 0) continue; 582 | 583 | // @Robustness: Do the zero-termination thing suggested in the RegQueryValue docs? 584 | 585 | wchar_t *lib_path = concat2(buffer, L"VC\\Lib\\amd64"); 586 | 587 | // Check to see whether a vcruntime.lib actually exists here. 588 | wchar_t *vcruntime_filename = concat2(lib_path, L"\\vcruntime.lib"); 589 | bool vcruntime_exists = os_file_exists(vcruntime_filename); 590 | free(vcruntime_filename); 591 | 592 | if (vcruntime_exists) { 593 | result->vs_exe_path = concat2(buffer, L"VC\\bin\\amd64"); 594 | result->vs_library_path = lib_path; 595 | 596 | free(buffer); 597 | RegCloseKey(vs7_key); 598 | return; 599 | } 600 | 601 | free(lib_path); 602 | free(buffer); 603 | } 604 | 605 | RegCloseKey(vs7_key); 606 | 607 | // If we get here, we failed to find anything. 608 | } 609 | 610 | 611 | Find_Result find_visual_studio_and_windows_sdk() { 612 | Find_Result result; 613 | 614 | find_windows_kit_root(&result); 615 | 616 | if (result.windows_sdk_root) { 617 | result.windows_sdk_um_library_path = concat2(result.windows_sdk_root, L"\\um\\x64"); 618 | result.windows_sdk_ucrt_library_path = concat2(result.windows_sdk_root, L"\\ucrt\\x64"); 619 | } 620 | 621 | find_visual_studio_by_fighting_through_microsoft_craziness(&result); 622 | 623 | return result; 624 | } 625 | 626 | 627 | #endif // MICROSOFT_CRAZINESS_IMPLEMENTATION_GUARD 628 | #endif // MICROSOFT_CRAZINESS_IMPLEMENTATION 629 | 630 | #ifdef __cplusplus 631 | } 632 | #endif -------------------------------------------------------------------------------- /src/os_windows.cpp: -------------------------------------------------------------------------------- 1 | #define NOMINMAX 2 | #define WIN32_LEAN_AND_MEAN 3 | #include 4 | 5 | #include "os_windows.h" 6 | 7 | char *get_absolute_path(const char *filename, Allocator allocator) { 8 | int absolute_path_buffer_size = 128; 9 | char *absolute_path = (char *)alloc(allocator, absolute_path_buffer_size); 10 | u32 result = GetFullPathNameA(filename, absolute_path_buffer_size, absolute_path, nullptr); 11 | if (result >= absolute_path_buffer_size) { 12 | absolute_path = (char *)alloc(allocator, result); 13 | u32 new_result = GetFullPathNameA(filename, result, absolute_path, nullptr); 14 | assert(new_result < result); 15 | } 16 | assert(absolute_path != nullptr); 17 | return absolute_path; 18 | } 19 | 20 | 21 | 22 | wchar_t *cstring_to_wide(char *str, Allocator allocator) { 23 | if (str == nullptr) { 24 | return nullptr; 25 | } 26 | int query_num_chars = MultiByteToWideChar(CP_ACP, 0, str, -1, nullptr, 0); 27 | if (query_num_chars <= 0) { 28 | return nullptr; 29 | } 30 | 31 | wchar_t *wide_string = (wchar_t *)alloc(allocator, (query_num_chars+1) * sizeof(u16), true); 32 | int result_num_chars = MultiByteToWideChar(CP_ACP, 0, str, -1, wide_string, query_num_chars); 33 | assert(result_num_chars == query_num_chars); 34 | return wide_string; 35 | } 36 | 37 | char *wide_to_cstring(wchar_t *wide, Allocator allocator) { 38 | if (wide == nullptr) { 39 | return nullptr; 40 | } 41 | int query_num_chars = WideCharToMultiByte(CP_ACP, 0, wide, -1, nullptr, 0, nullptr, nullptr); 42 | if (query_num_chars <= 0) { 43 | return nullptr; 44 | } 45 | assert(query_num_chars > 0); 46 | 47 | char *cstring = (char *)alloc(allocator, query_num_chars+1, true); 48 | int result_num_chars = WideCharToMultiByte(CP_ACP, 0, wide, -1, cstring, query_num_chars, nullptr, nullptr); 49 | assert(result_num_chars == query_num_chars); 50 | return cstring; 51 | } 52 | 53 | char *get_current_exe_name(Allocator allocator) { 54 | wchar_t exe_path_wide[MAX_PATH]; 55 | GetModuleFileNameW(nullptr, exe_path_wide, MAX_PATH); 56 | return wide_to_cstring(exe_path_wide, allocator); 57 | } 58 | 59 | void delete_file(char *filename) { 60 | DeleteFileA(filename); 61 | } 62 | 63 | 64 | 65 | void init_timer(Timer *timer) { 66 | LARGE_INTEGER large_integer_frequency = {}; 67 | bool ok = QueryPerformanceFrequency(&large_integer_frequency) != 0; 68 | assert(ok); 69 | timer->frequency = large_integer_frequency.QuadPart/1000.0; 70 | } 71 | 72 | double query_timer(Timer *timer) { 73 | LARGE_INTEGER large_integer_counter = {}; 74 | bool ok = QueryPerformanceCounter(&large_integer_counter) != 0; 75 | assert(ok); 76 | return large_integer_counter.QuadPart / timer->frequency; 77 | } 78 | 79 | 80 | 81 | Mutex create_mutex() { 82 | HANDLE handle = CreateMutex(nullptr, false, nullptr); 83 | Mutex mutex = {}; 84 | mutex.handle = handle; 85 | return mutex; 86 | } 87 | 88 | bool wait_for_mutex(Mutex mutex) { 89 | DWORD wait_result = WaitForSingleObject(mutex.handle, INFINITE); 90 | assert(wait_result == WAIT_OBJECT_0); 91 | return true; 92 | } 93 | 94 | void release_mutex(Mutex mutex) { 95 | bool result = ReleaseMutex(mutex.handle); 96 | assert(result); 97 | } 98 | 99 | Thread create_thread(u32 (*function)(void *userdata), void *userdata) { 100 | DWORD id = {}; 101 | HANDLE handle = CreateThread( 102 | NULL, // default security attributes 103 | 0, // use default stack size 104 | (DWORD (*)(void *))function, // thread function name 105 | userdata, // argument to thread function 106 | 0, // use default creation flags 107 | &id); 108 | Thread thread = {}; 109 | thread.handle = handle; 110 | return thread; 111 | } 112 | 113 | void wait_for_thread(Thread thread) { 114 | DWORD result = WaitForSingleObject(thread.handle, INFINITE); 115 | assert(result == WAIT_OBJECT_0); 116 | } 117 | 118 | 119 | 120 | void sleep(u64 ms) { 121 | Sleep(ms); 122 | } -------------------------------------------------------------------------------- /src/os_windows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "basic.h" 4 | 5 | struct Timer { 6 | double frequency = {}; 7 | }; 8 | 9 | void init_timer(Timer *timer); 10 | double query_timer(Timer *timer); 11 | 12 | 13 | 14 | char *get_absolute_path(const char *filename, Allocator); 15 | wchar_t *cstring_to_wide(char *str, Allocator allocator); 16 | char *wide_to_cstring(wchar_t *wide, Allocator allocator); 17 | char *get_current_exe_name(Allocator); 18 | 19 | void delete_file(char *); 20 | 21 | 22 | 23 | void sleep(u64 ms); 24 | 25 | typedef void *Handle; 26 | 27 | struct Mutex { 28 | Handle handle; 29 | }; 30 | 31 | Mutex create_mutex(); 32 | bool wait_for_mutex(Mutex mutex); 33 | void release_mutex(Mutex mutex); 34 | 35 | struct Thread { 36 | Handle handle; 37 | }; 38 | 39 | Thread create_thread(u32 (*function)(void *userdata), void *userdata); 40 | void wait_for_thread(Thread thread); -------------------------------------------------------------------------------- /src/spinlock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Spinlock { 6 | std::atomic_flag flag; 7 | 8 | Spinlock() { 9 | flag.clear(); 10 | } 11 | 12 | Spinlock(const Spinlock &tm) = delete; 13 | Spinlock(Spinlock &&tm) = delete; 14 | Spinlock& operator=(const Spinlock &tm) = delete; 15 | Spinlock& operator=(Spinlock && tm) = delete; 16 | 17 | void lock() { 18 | while(flag.test_and_set(std::memory_order_acquire)) { } 19 | } 20 | 21 | bool try_lock() { 22 | return flag.test_and_set(std::memory_order_acquire) == false; 23 | } 24 | 25 | void unlock() { 26 | flag.clear(std::memory_order_release); 27 | } 28 | }; --------------------------------------------------------------------------------