demotomohiro / nim-4k-intro-sample

4k intro sample code written with Nim programming language.
MIT License
14 stars 1 forks source link

Error: undeclared identifier: 'specialWords' in openGL4kGen.nim #1

Closed plicit closed 1 year ago

plicit commented 1 year ago

Hi and thanks for sharing your code! :)

I just started exploring Nim and tried your 4k-intro but it turns out that specialWords no longer exists in Nim's compiler/wordrecg, so compilation fails:

openGL4kGen.nim(91, 7) Error: undeclared identifier: 'specialWords' https://github.com/demotomohiro/nim-4k-intro-sample/blob/master/tools/openGL4kGen/openGL4kGen.nim#L89-L91

With some experimenting, I figured out a way to generate a Sequence of the same strings (I think), though it executes at runtime:

var specialWords = newSeq[string]()
for kw in TSpecialWord.low.succ .. nimKeywordsHigh.TSpecialWord:
  specialWords.add($kw)

Do you know if there is a better way to create a ranged subset of an enums strings at compiletime in Nim?

demotomohiro commented 1 year ago

Thank you for reporting. I fixed errors in openGL4kGen.

plicit commented 1 year ago

Great, thanks! That's a neat fix with sugar collect.

minimumGLTriangle.nim now compiles the default version correctly, but Crinkler breaks the danger version:

So, Crinkler 2.3 (Jul 21 2020) is either broken or incompatible with Visual Studio 2022 obj files (the VS 2015 installer gives me an error that it can't find BuildTools_MSBuild.msi when installing).

Microsoft (R) Incremental Linker Version 14.36.32534.0 Copyright (C) Microsoft Corporation. All rights reserved.

/out:@mC@c@s_scoop@sapps@snim@scurrent@slib@ssystem@sio.nim.c.exe user32.lib kernel32.lib Gdi32.lib Opengl32.lib /SUBSYSTEM:WINDOWS /entry:WinMainCRTStartup /OUT:D:\data\dev\nim\demotomohiro_nim-4k-intro-sample\src\minimumGLTriangle.exe [...]\@mC@c@s_scoop@sapps@snim@scurrent@slib@ssystem@sio.nim.c.obj [...]\@mC@c@s_scoop@sapps@snim@scurrent@slib@ssystem.nim.c.obj [...]\@mopenGL4k2.nim.c.obj [...]\@mminimumGLTriangle.nim.c.obj

LIBCMT.lib(exe_winmain.obj) : error LNK2019: unresolved external symbol _WinMain@16 referenced in function "int cdecl scrt_common_main_seh(void)" (?__scrt_common_main_seh@@YAHXZ) [...]\src\minimumGLTriangle.exe : fatal error LNK1120: 1 unresolved externals

Error: execution of an external program failed: 'vccexe.exe
[...]\@mC@c@s_scoop@sapps@snim@scurrent@slib@ssystem@sio.nim.c.obj [...]\@mC@c@s_scoop@sapps@snim@scurrent@slib@ssystem.nim.c.obj [...]\@mopenGL4k2.nim.c.obj [...]\@mminimumGLTriangle.nim.c.obj /link user32.lib kernel32.lib Gdi32.lib Opengl32.lib /SUBSYSTEM:WINDOWS /entry:WinMainCRTStartup /OUT:[...]\minimumGLTriangle.exe'



- Of course, the `csrc` build = 4 KB without crinkler and works
demotomohiro commented 1 year ago

Seems VS 2019 works but VS 2022 doesn't with Crinkler: https://github.com/runestubbe/Crinkler/issues/13

If minimumGLTriangle.nim doesn't compile well, try compiling minimum.nim. minimum.nim is simpler than minimumGLTriangle.nim and easier to find a problem.

Make sure Nim calls C compiler for 32bit x86 CPU. Crinkler doesn't support x64 and people creating 4k intro or small size intro don't create 64bit binary as it likely larger than 32bit binary. Nim should call 32bit one when --cc:vcc and --cpu:i386 options are set. You can check how Nim calls backend C compiler by adding --listcmd option.

plicit commented 1 year ago

Thanks for your help! Sorry I missed that issue on Crinkler -- unfortunately, it still doesn't work with VS2019 (I even uninstalled all other VS versions). I'll try VS2015 once I figure out why its installer is failing.

Does Crinkler 2.3 work for you with minimum.c and VS2019 build tools?

minimum.c:

#include <windows.h>
void WinMainCRTStartup(void) {
  MessageBoxA(0, "foobar", "title", 0);
}

Build:

Microsoft Windows [Version 10.0.19045.2965]
(c) Microsoft Corporation. All rights reserved.

D:\dev> "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars32.bat"
**********************************************************************
** Visual Studio 2019 Developer Command Prompt v16.11.27
** Copyright (c) 2021 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x86'

D:\dev> cl /c minimum.c
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30151 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

minimum.c

D:\dev> crinkler /subsystem:windows /OUT:minimum_crinkler.exe user32.lib kernel32.lib minimum.obj
Crinkler 2.3 (Jul 21 2020) (c) 2005-2020 Aske Simon Christensen & Rune Stubbe

Target: minimum_crinkler.exe
Tiny compressor: NO
Tiny import: NO
Subsystem type: WINDOWS
Large address aware: NO
Compression mode: SLOW
Saturate counters: NO
Hash size: 500 MB
Hash tries: 100
Order tries: 0
Reuse mode: OFF (no file specified)
Report: NONE
Transforms: NONE
Replace DLLs: NONE
Fallback DLLs: NONE
Range DLLs: NONE
Exports: NONE

Loading user32.lib...
Loading kernel32.lib...
Loading minimum.obj...

Linking...

Uncompressed size of code:   163
Uncompressed size of data:    27

|-- Estimating models for code ----------------------------|
............................................................   0m00s
Estimated compressed size of code: 132.71

|-- Estimating models for data ----------------------------|
............................................................   0m00s
Estimated compressed size of data: 20.95

Ideal compressed size of code: 132.71
Ideal compressed size of data: 20.95
Ideal compressed total size: 153.66

|-- Optimizing hash table size ----------------------------|
............................................................   0m00s
Real compressed total size: 154
Bytes lost to hashing: 0.34

Output file: minimum_crinkler.exe
Final file size: 442

time spent: 0m00s

minimum_crinkler.exe seems to start but does nothing and then exits. If I change the subsystem to console, it opens a console window, pauses and then exits.

Regarding your Nim-4k, the "release" size of the .exes are ~100 KB, so I doubt Crinkler could actually get that down to 4k.

My package setup is roughly:

choco upgrade visualstudio2019buildtools --package-parameters "--includeRecommended"
choco upgrade visualstudio2019-workload-vctools
scoop install crinkler

And here is a .bat build script that clones your repo and downloads OpenGL headers and cacert.pem needed for openGL4kGen.exe and then builds a few .exe:

build-nim4kintro.bat:

SetLocal EnableDelayedExpansion

@echo --------------------------------
@echo -- Setup environment:
@echo --------------------------------

set NIM4K=%~dp0\nim-4k-intro-sample

IF NOT EXIST "!NIM4K!" (
git clone https://github.com/demotomohiro/nim-4k-intro-sample
@echo on
)

pushd "!NIM4K!"

@echo --------------------------------
@echo -- Setup Visual Studio 2019 32-bit
@echo --------------------------------

set OPENGL=!NIM4K!\OpenGL

WHERE cl.exe

IF %ERRORLEVEL% NEQ 0 (
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars32.bat"
@echo on
set INCLUDE=!OPENGL!;!INCLUDE!
@echo INCLUDE = !INCLUDE!
@rem LIB should already be set by vcvars32
@rem set LIB=c:\Program Files (x86)\Windows Kits\10\Lib\10.0.20348.0\um\x86;%LIB%
)

@echo --------------------------------
@echo -- Download OpenGL headers
@echo --------------------------------

IF NOT EXIST "!OPENGL!\GL\glext.h" (
@echo DOWNLOADING OPENGL
mkdir "!OPENGL!\GL"
pushd "!OPENGL!\GL"
curl -LO https://registry.khronos.org/OpenGL/api/GL/glext.h
curl -LO https://registry.khronos.org/OpenGL/api/GL/wglext.h
curl -LO https://registry.khronos.org/OpenGL/api/GL/glxext.h
curl -LO https://registry.khronos.org/OpenGL/api/GL/glcorearb.h
popd

mkdir "!OPENGL!\KHR"
pushd "!OPENGL!\KHR"
curl -LO https://registry.khronos.org/EGL/api/KHR/khrplatform.h
popd
)

@echo --------------------------------
@echo -- Build openGL4kGen and generate files
@echo --------------------------------

IF NOT EXIST "!NIM4K!\tools\openGL4kGen\openGL4kGen.exe" (
pushd "tools\openGL4kGen"
@rem cacert.pem is a requirement
curl -LO https://curl.se/ca/cacert.pem
nim c -d:ssl openGL4kGen.nim
openGL4kGen.exe > ..\..\src\openGL4k2.nim
popd
)

@echo --------------------------------
@echo -- Build some csrc .exe
@echo --------------------------------

pushd csrc

@rem these use Crinkler and are broken:
call minimum_build.bat
call minimumGLTriangle_build.bat

@rem minimumGLTriangle_build.bat created triangle.vs.h needed to build, so we don't have to:
@rem nim e glslheader.nims ..\shaders\triangle.vs ..\shaders\triangle.fs 

@echo Build a .exe from C without Crinkler (works 4k)
cl.exe /nologo /O1 /Ob2 /Oi /Os minimumGLTriangle.c /link user32.lib kernel32.lib Gdi32.lib Opengl32.lib /SUBSYSTEM:WINDOWS /entry:WinMainCRTStartup /out:minimumGLTriangle.exe

@rem Crinkler would break our .exe, too:
@rem crinkler /OUT:minimumGLTriangle_c.exe /SUBSYSTEM:WINDOWS /entry:WinMainCRTStartup user32.lib kernel32.lib Gdi32.lib Opengl32.lib minimumGLTriangle.obj
popd

@echo --------------------------------
@echo -- Build some src .exe
@echo --------------------------------

pushd src
@rem works ~209 KB:
nim c minimum.nim
nim c minimumGLTriangle.nim

@rem works ~138 KB:
nim c -d:release --out:minimum.release.exe minimum.nim
nim c -d:release --out:minimumGLTriangle.release.exe minimumGLTriangle.nim

@rem broken 442 bytes and 875 bytes:
nim c -d:danger --out:minimum.danger.crinkled  minimum.nim
nim c -d:danger --out:minimumGLTriangle.danger.crinkled  minimumGLTriangle.nim
popd

rem exit NIM4K directory
popd

@echo --------------------------------
@echo -- DONE
@echo --------------------------------
demotomohiro commented 1 year ago

Like Nim was used to write malware and executables compiled with Nim were detected by antivirus softwares even if it is not virus, Crinkler was used by malware writers and executables generated by Crinkler were sometimes detected by antivirus softwares. Your antivirus software might stop running minimum_crinkler.exe.

When compiled without -d:danger, src/config.nims doesn't change linker settings, and a linker in visual studio is called by Nim. In that case, C standard library/runtime is linked and generated executable become bigger. When compiled with -d:danger, crinker is called and C standard library/runtime is not linked.

plicit commented 1 year ago

It turns out that Nim wants to link the CRT lib regardless of -d:danger due to unresolved external symbols in lib\system\io.nim:

__setmode
___acrt_iob_func
__fileno

However, lib\system\io.nim only loads those symbols when windows is defined AND nimscript is NOT defined, so I tried defining nimscript and it worked!

nim c --listcmd -d:release -d:nimscript -d:danger minimumGLTriangle.nim

That minimumGLTriangle.exe is under 4k even without crinkler (which is broken on the Win10's that I've tried). Ideally, Nim should have a flag for excluding the CRT rather than abusing nimscript, but at least there is a way.

Here's my config.nims for posterity:

--cc:vcc
--d:noAutoGLerrorCheck
--cpu:i386
--os:standalone
--gc:none
--dynlibOverrideAll
--noMain
--opt:size
#switch("d", "StandaloneHeapSize=0")
switch("d", "windows")

put "vcc.options.always", "/nologo /Ob2 /Oi /Os /GS-"
put "vcc.options.linker", "/link /SUBSYSTEM:WINDOWS /entry:WinMainCRTStartup /nodefaultlib"

if not existsFile("openGL4k2.nim"):
  withDir "../tools/openGL4kGen":
    selfExec("c -d:ssl openGL4kGen.nim")
    exec("openGL4kGen.exe -o=../../src/openGL4k2.nim")

Thanks for all your help! :)