Nintendo Switch पर SBCL पोर्टिंग
(reader.tymoon.eu)- Trial गेम इंजन को Nintendo Switch पर चलाने के लिए Common Lisp runtime SBCL को 2 साल तक port किया गया, और फिलहाल Switch पर Lisp code compile/run करने और shared libraries से जुड़ने तक पहुंच गया है
- Switch का environment ARM64 Cortex-A57 4-core, 4GB RAM है, लेकिन shell, command line और compiler नहीं हैं, और executable pages बनाना भी रोका गया है, इसलिए यह SBCL के सामान्य bootstrap तरीके से मेल नहीं खाता
- Build structure Linux ARM64 host और Nintendo SDK के संयोजन पर आधारित है;
fasteval,immobile-code, और elfination के जरिए core को code और data में बांटकर final package में bundle किया जाता है - Trial REPL example Switch devkit पर OpenGL context, input handling और shader allocation तक करता है, लेकिन garbage collector चलते ही crash हो जाता है और C callback issue की वजह से audio अभी संभव नहीं है
- बाकी मुख्य कामों में multithreaded GC के लिए safepoint को stabilize करना, C callbacks restore करना, CLOS runtime compilation से बचना और performance optimization शामिल हैं; Nintendo NDA की वजह से कुछ code public नहीं किया जा सकता
Switch पर SBCL कितना काम कर रहा है
- Trial game engine को Nintendo Switch पर run कराने के लिए Charles Zhang के साथ पिछले 2 साल से SBCL porting पर काम किया गया
- मुख्य कठिनाई Trial के नीचे चलने वाले Common Lisp runtime को Switch platform के मुताबिक ढालना था
- अभी क्या संभव है:
- Runtime और compiler port कर दिए गए हैं, जिससे Switch पर arbitrary Lisp code को सीधे compile और run किया जा सकता है
- Shared libraries से interface किया जा सकता है
- Trial को Switch पर जिन कई operating-system portability libraries की जरूरत है, उन्हें भी port किया गया है
- Trial का REPL example Switch devkit पर चलता है
- Switch में terminal नहीं है, इसलिए Trial OpenGL context बनाता है, input manage करता है, shaders allocate करता है और screen पर text दिखाता है
- अभी अटकी हुई चीजें:
- SBCL जब garbage collector चलाने की कोशिश करता है, तो जल्दी crash हो जाता है
- Switch की खास constraints की वजह से कुछ हिस्से अभी bypass नहीं हो पाए हैं
- C callback mechanism टूटा हुआ है, इसलिए audio output संभव नहीं है
- Performance से जुड़े कुछ और issues अभी सामने आ सकते हैं
लागत और sustainability
- Porting work पर अब तक करीब 17,000 dollars खर्च हुए हैं, और Charles Zhang को monthly basis पर payment किया जाता रहा है
- Income Kandria sales और Patreon, GitHub Sponsors, Ko-Fi support से आती है
- अच्छे महीनों में करीब 1,200 dollars
- खराब महीनों में करीब 600 dollars
- Zürich, Switzerland में सिर्फ इस income पर रहना मुश्किल है, और फिलहाल लेखक माता-पिता के साथ रहकर personal expenses घटाकर काम चला रहे हैं
- Switch के लिए Kandria sales से porting cost recover होगी या नहीं, इस पर संदेह है, इसलिए limited resources कहां खर्च किए जाएं यह तय करना कठिन हो गया है
- Sponsorship income फिलहाल SBCL Switch porting और अभी चल रहे unreleased game project पर खर्च की जाएगी
Switch environment और porting conditions
- Publicly known Switch environment:
- User code ARM64 Cortex-A57 chip पर चलता है
- 4 cores और 4GB RAM उपलब्ध हैं
- Nintendo 3DS के लिए पहले develop किए गए proprietary microkernel operating system पर चलता है
- SBCL में पहले से ARM64 Linux port है, इसलिए code generation वाला हिस्सा solved है
- Kandria आसानी से 4GB RAM में fit हो जाता है, इसलिए memory capacity खुद समस्या नहीं है
- असली कठिनाई Switch के proprietary operating system से interface करने में है
- इसमें ऐसी constraints हैं जो सामान्य PC operating systems में नहीं होतीं
- Lisp जैसे systems के लिए यह खासतौर पर problem है
- Switch एकमात्र console है जो Trial के आधार OpenGL rendering को support करता है
- Xbox केवल DirectX support करता है, लेकिन Microsoft द्वारा develop की गई OpenGL → DirectX layer होने की बात known है, इसलिए संभावना हो सकती है
- Playstation के बारे में पूरी तरह proprietary graphics API होने की जानकारी है, इसलिए उस platform पर porting अभी भी ऐसी चीज है जिस पर विचार नहीं करना चाहते
- Development के लिए Nintendo of Europe से access मिला और करीब 400 dollars का devkit खरीदा गया
- Devkit और SDK सिर्फ Windows पर चलते हैं, और आगे build process में यह बोझ बन गया
सामान्य SBCL build और Switch के साथ टकराव
- SBCL मुख्यतः Lisp में ही लिखा गया है, और इसमें एक छोटा C runtime भी शामिल है
- C runtime को सामान्य C compiler से build किया जाता है, लेकिन target operating system environment की जानकारी चाहिए होती है
- Runtime में खुद Lisp compiler नहीं होता, इसलिए SBCL को bootstrap करने के लिए कोई दूसरा Lisp implementation चाहिए होता है, और ideally SBCL का कोई दूसरा version इस्तेमाल होता है
-
PC पर 5-step build
build-config- Target के build configuration options इकट्ठा करके उन्हें ऐसे format में output करता है जिसे आगे के build steps पढ़ सकें
make-host-1- Host Lisp compiler से cross compiler build करता है
- Lisp objects के memory layout को C structs के रूप में describe करने वाली header files भी generate करता है
make-target-1- Target C compiler से C runtime बनाता है
- C runtime में garbage collector और operating system environment के साथ glue code शामिल होता है
- Operating system headers से वे constants भी generate करता है जिन्हें target Lisp compiler और runtime को जानना होता है
make-host-2make-host-1में बने Lisp cross compiler से target Lisp system, यानी compiler और standard library build करता है- ऐसा cold core बनाता है जिसमें runtime enter कर सकता है
make-target-2- cold core को target runtime में load करके bootstrap पूरा करता है
- Lisp system memory में load होने के बाद warm core के रूप में dump होता है
- इसके बाद नया code load किया जा सकता है और नई images freely dump की जा सकती हैं
-
Switch पर problem points
- SBCL build में target machine पर Lisp code execute करने वाला step चाहिए
- User Lisp code को C की तरह simple batch compilation से handle नहीं किया जा सकता, और run होते समय उसके target environment में होने की assumption होती है
- Application deployment भी
make-target-2जैसा है: Lisp code को incrementally compile करके runtime से जुड़ा core dump किया जाता है - SBCL runtime start होते समय core blob को memory में map करता है, code pages को executable mark करता है, और custom entry function पर jump करता है
- Switch पर यह पूरा तरीका problem बन जाता है
Switch के लिए build strategy
- Switch PC environment नहीं है और इसमें shell, command line या compiler suite नहीं है
- Operating system executable pages creation allow नहीं करता, इसलिए अगर build steps Switch पर चला भी पाएं, तो भी सामान्य Lisp incremental compilation possible नहीं है
- ज्यादातर code platform-independent है, इसलिए ARM64 के लिए compile किया जा सकता है
- केवल surrounding operating system environment को छूने वाले हिस्सों को यह बताया जा सकता है कि target Switch है, और implementation Linux जैसे किसी दूसरे ARM64 environment पर बनाई जा सकती है
-
Switch के लिए build steps
build-config- Host system पर run होता है और Switch build बताने वाले special flags इस्तेमाल करता है
fastevalcontrib enable करता है- Switch पर runtime compilation नहीं हो सकती, इसलिए
fastevalउन जगहों की जगह लेता है जहां compiler call चाहिए होता है
make-host-1- बड़ा बदलाव नहीं है और Switch platform preparation के लिए headers generate करता है
make-target-1- Nintendo SDK द्वारा दिए गए C compiler से Switch के लिए C runtime cross-compile करता है
- Switch OS POSIX-compatible नहीं है, इसलिए SBCL में custom runtime target बनाया गया, और dynamic linking, page mapping जैसे OS differences को stub या wrap किया गया
- दूसरा
build-config,make-host-1,make-target-1- Switch जैसी feature set वाला सामान्य ARM64 Linux system बनाता है
- कुछ Lisp processes में final target Switch है यह बताने के लिए special flags इस्तेमाल होते हैं
make-host-2,make-target-2- थोड़ा special Linux ARM64 SBCL build मिलता है, और बाद में user code compile किया जाता है
- User code compilation
*features*बदला जाता है ताकि उसे लगे कि वह Linux पर नहीं, Switch पर run हो रहा है:nxशामिल करता है और:linux,:unix,:posixको हटाता है- ASDF को disable करने के बाद Trial जैसे programs को normal तरीके के करीब compile करता है और नया core dump करता है
immobile-code और elfination से core packaging
- Switch पर सामान्य core mapping strategy इस्तेमाल नहीं की जा सकती, इसलिए Switch runtime में नया core attach करने वाला तरीका काम नहीं करता
- Solution के तौर पर SBCL के relatively lesser-known features
immobile-codeऔर elfination इस्तेमाल किए गए - सामान्य SBCL runtime पर compiled code को किसी page में रखता है और उस page को executable mark करता है
- बाद में code की जरूरत न रहे तो वह garbage collection target हो सकता है
- Space reclaim किया जा सकता है और बाकी code compact किया जा सकता है
immobile-codeअलग strategy इस्तेमाल करता है- Code को specially reserved code pages में रखता है और वहीं बनाए रखता है
- Code garbage collect नहीं किया जा सकता
- बदले में traditional operating system executable support का उपयोग किया जा सकता है
- Executable files में pre-marked sections होते हैं जिन्हें OS code के रूप में जानता है, और program start पर OS mapping handle करता है
- elfination step core को normal executable के लिए जरूरी separate code और data sections में बदलता है
- elfinator core को analyze करता है
- Address space layout randomization के लिए जरूरी position-independent code बनाने हेतु assembly को rewrite करता है
- Pure code assembly file और pure data payload file में separate करता है
- Final steps:
- elfinator से assembly file generate करना
- Final binary link करना
- Nintendo SDK के authoring tools से metadata, shared libraries, assets और application binary को एक final package में bundle करना
Build infrastructure और public किए जा सकने वाला scope
- Build के लिए कम से कम ज्यादातर build करने वाली ARM64 Linux machine चाहिए
- Nintendo SDK compiler और authoring tools चलाने के लिए AMD64 Windows machine या Wine इस्तेमाल करने वाली AMD64 Linux machine चाहिए
- Actual setup कुल मिलाकर लगभग तीन machines जैसा है
- AMD64 “driver”
- ARM64 build host
- Devkit से communicate करने वाली Windows VM
- Caching और machines के बीच synchronization logic वाला special build system लिखकर automation किया गया
- इसे MSYS2/Windows environment में भी चलना था, इसलिए path conversion issues थे
- ARM64 पर elfinator और
immobile-codeको काम कराना पड़ा, औरpathname-utils,libmixed,cl-gamepadजैसी support libraries भी port की गईं - Nintendo NDA की वजह से बहुत सी details public नहीं की जा सकतीं
- जो काम public किया जा सकता था, उसे upstream किया गया, और Lisp libraries के private forks नहीं रखे गए
- Nintendo SDK से direct link न करने के लिए अलग C library बनाई गई, और Lisp libraries इस custom interface के जरिए operating system functionality access करती हैं
- Structure ऐसा है कि Lisp side public रहे और सिर्फ छोटी C library private रखी जाए
Absolute pointers और load-time relocation
- elfination शुरुआत से position-independent executable Lisp code बनाने के लिए design किया गया feature नहीं था
- Lisp code में आम तौर पर बहुत सारे absolute pointers होते हैं
- SBCL compiler और runtime में load-time absolute pointer relocation support करने के लिए काम करना पड़ा
- Code objects में आमतौर पर code constants होते हैं, और यह सुनिश्चित करना था कि उनमें अब absolute pointers न हों
- GC executable section modify नहीं कर सकता
- OS loader भी absolute pointer relocation के लिए executable section modify नहीं कर सकता
- Solution:
- Code constants जैसे absolute pointers को text area के बाहर read/write-capable space में move किया गया
- Code के अंदर constant references को इस read/write space से load करने के लिए rewrite किया गया
- Loader और moving GC उस space के pointers modify कर सकते हैं
Garbage collector और safepoint problem
- SBCL का standard GC gencgc है, यानी generational garbage collector
- gencgc object generations को separate करता है, अलग-अलग frequency पर scan करता है, और objects को दूसरी generation location में copy करके space compact करता है
- यह structure खुद Switch पर inherently problem नहीं है, लेकिन multithreading problem पैदा करती है
- अगर कई threads हों, तो कोई thread object access कर रहा हो सकता है, इसलिए objects को arbitrarily move नहीं किया जा सकता
- सबसे आसान solution GC शुरू होने से पहले सभी threads को रोक देना है
-
Unix और Switch का अंतर
- Unix systems में signal mechanism से दूसरे threads को signal भेजकर park कराया जा सकता है
- Switch में signal mechanism नहीं है, और thread को interrupt भी नहीं किया जा सकता
- हर thread को खुद यह detect करना होगा कि उसे park करना है, और typical strategy safepoint है
- Safepoint में compiler extra code insert करता है ताकि thread check करे कि उसे park करना है या नहीं
- Checks add करने की cost होती है, इसलिए इन्हें जितना हो सके कम रखना पड़ता है
- Checks बहुत rare हों तो सभी threads के park होने तक GC शुरू नहीं हो सकता, जिससे दूसरे threads रुक जाते हैं
- Check में बहुत instructions लगें तो CPU cache lines और pipelining optimizations में बाधा आती है
-
मौजूदा SBCL safepoint की limits
- SBCL का current safepoint system Windows के लिए लिखा गया है
- Windows में भी inter-process signal handler नहीं है, लेकिन Switch के विपरीत current thread के लिए signal handling है
- मौजूदा तरीका:
- हर thread एक page रखता है जिसमें safepoint एक word लिखता है
- GC शुरू होने पर उस page को read-only mark किया जाता है
- दूसरा thread safepoint पर पहुंचकर page में write करने की कोशिश करता है तो segmentation fault होता है और वह thread park हो जाता है
- सिर्फ एक write instruction चाहिए, इसलिए efficient है
- Switch पर यह technique भी इस्तेमाल नहीं की जा सकती, इसलिए ज्यादा complex checks insert करने पड़ेंगे
- Safepoint Windows के अलावा platforms पर जरूरी नहीं था, इसलिए दूसरी जगह test नहीं हुआ, और Switch-specific fixes से अलग भी unstable है
- Codebase में उस हिस्से की बड़ी cleanup करनी पड़ सकती है, लेकिन उम्मीद है कि full rewrite तक बात न पहुंचे
CLOS runtime compilation से बचना
- CLOS method dispatch के लिए जरूरी discriminating function compilation को आम तौर पर generic function की पहली call तक defer करता है
- CLOS बहुत dynamic है और methods लगभग कभी भी add/delete किए जा सकते हैं, इसलिए system complete कब हुआ यह जानना कठिन है
- Switch पर compiler call नहीं किया जा सकता, इसलिए यह delayed compilation तरीका जस का तस इस्तेमाल नहीं हो सकता
- Current strategy fast evaluator पर निर्भर है
compilefunction को stub किया गया है- Compilation के बजाय evaluator से code run करने वाला lambda generate किया जाता है
compileपर depend करने वाले user code के साथ भी काम करता है- Real compilation की तुलना में execution speed काफी धीमी है
बाकी काम और performance risks
fastevalapproach मुख्यतः fallback है- Final image dump करने से ठीक पहले ज्यादा से ज्यादा CLOS state को freeze करने और जितना संभव हो code precompile करने के तरीके explore करने का इरादा है
- Charles ने कुछ साल पहले restore किया था वह block compilation mode भी और investigate किया जाएगा
- Switch के अपेक्षाकृत कमजोर processor की वजह से additional optimization की जरूरत पड़ सकती है
- Trial engine side
- Kandria code side
- अब तक 10 साल पुराने computers भी game requirements को पर्याप्त रूप से handle कर लेते थे, इसलिए optimization relatively कम करने से चल गया था
- High-priority work:
- Garbage collector को पूरी तरह काम करने लायक बनाना
- अभी boot के बाद Trial main loop तक पहुंचा जा सकता है
- multi-generation compaction तक पहुंचने पर fail हो जाता है
- C से आने वाले callbacks फिर से काम कराने हैं
- SBCL codebase के उस हिस्से में hand-written assembly routines बहुत हैं
immobile-codeऔर elfination के लिए adjustment की जरूरत हो सकती है- Trial को callbacks सिर्फ
libmixedके जरिए sound playback के लिए चाहिए
- Trial की selftest suite Switch पर पूरी तरह pass होने से पहले जरूरी extra portability feature work
- Garbage collector को पूरी तरह काम करने लायक बनाना
NDA की वजह से सीमित public scope
- पूरा porting work public करना चाहते हैं, लेकिन Nintendo NDA की वजह से नहीं कर सकते
- जो हिस्सा public किया जा सकता है, उसे upstream या publish किया गया है
- Nintendo SDK से directly connected कुछ code NDA sign न करने वाले लोगों के साथ share नहीं किया जा सकता
- अगर कोई Nintendo Switch पर Common Lisp game release करना चाहता है, तो NDA sign करने के बाद porting work का access मिल सकता है
- Patrons के लिए monthly updates में progress और detail में share की जाएगी
1 टिप्पणियां
Hacker News की टिप्पणियाँ
पिछले कुछ हफ्तों में Common Lisp में game development आज़माने के लिए मैंने Trial(https://github.com/Shirakumo/trial) इस्तेमाल किया, और यह काफी मज़ेदार रहा
game चलते रहने के दौरान लगभग हर चीज़ बदल पाने की क्षमता सचमुच बहुत बड़ा फायदा है, इसलिए उम्मीद है कि यह port सफल होगा
code changes के बीच application state बनाए रख पाना भी बहुत उपयोगी लगता है, और Common Lisp भी मेरी धुंधली धारणा से कहीं ज़्यादा तेज़ भाषा लगती है
निजी तौर पर, सिर्फ game development ही नहीं बल्कि कुल मिलाकर data structures का इस्तेमाल, खासकर maps को handle करने का तरीका, इसका सबसे बड़ा rough edge लगता है; लेकिन यह trade-off सहने लायक लगता है
यह वाकई शानदार है। SBCL एक बेहतरीन language implementation है, और मैं “असल” game console पर Common Lisp development करके देखना चाहता था
Shinmera का यह काम करना भी अच्छे अर्थ में चौंकाने वाला है। पहले #lispgames और Lisp Discord पर उन्हें कुछ बार देखा था, लेकिन नहीं पता था कि उन्हें ऐसे low-level development में भी दिलचस्पी है
मैंने कभी SBCL internals में थोड़ी देर झाँका था और डरकर पीछे हट गया था, इसलिए यह और भी प्रभावशाली लगता है। यह भी जानने की उत्सुकता है कि SBCL में threading और SDL2 जोड़कर बनी combination अभी Raspberry Pi पर चलती है या नहीं
और जैसा ऊपर कहा, *her :)
इतना रोचक और detailed लेख लिखने के लिए लेखक को धन्यवाद। इस स्तर की official console porting details आमतौर पर console की life खत्म होने के कई साल बाद ही सामने आती हैं, ऐसा लगता है
इतना गहरा काम पढ़ते हुए, दिन भर repetitive software इस्तेमाल करने वाली अपनी job याद आती है और थोड़ी ईर्ष्या भी होती है
devkits में आमतौर पर बेहतर hooks होते थे, लेकिन साथ ही GDB-केंद्रित flow से बचने का फायदा भी था। दोनों तरफ का अनुभव लेने के बाद, मुझे नहीं पता कि वह दुनिया सच में इतनी ईर्ष्या करने लायक है या नहीं
शायद यह कोई ऐसा project नहीं है जिसे मेरे ध्यान की ज़रूरत है, बल्कि ऐसा project है जिसकी ज़रूरत मेरे ध्यान को बाद में पड़ेगी :D
“सब कुछ public करना चाहता/चाहती हूँ, लेकिन NDA की वजह से ऐसा नहीं कर सकता/सकती” वाला हिस्सा देखकर, यह सवाल उठा कि बिना restrictions वाले homebrew SDK(https://switchbrew.org/wiki/Setting_up_Development_Environment) की जगह official SDK क्यों इस्तेमाल किया गया
पूरी तरह अनुमान लगाऊँ तो, शायद इसलिए कि Nintendo third-party SDK से build किए गए games की official release की अनुमति नहीं देता
इससे संबंधित https://opengoal.dev है
background देखें तो Naughty Dog ने PS2 की Jak & Daxter series बनाते समय GOAL नाम की, Lisp जैसी अपनी भाषा इस्तेमाल की थी। उन्होंने पर्याप्त debugging information छोड़ी थी, इसलिए reverse engineering संभव हुई, और OpenGOAL project ने यह कर दिखाया
अब इन games को उन सभी platforms पर चलाया जा सकता है जहाँ GOAL compiler port हो चुका है। मेरी जानकारी में अभी यह लगभग x86 तक है, और Switch पर port हो जाए तो कमाल होगा
अभी-अभी Kandria खरीदा। मैं बहुत ज़्यादा games नहीं खेलता, इसलिए शायद ज्यादा play न करूँ, लेकिन Shinmera साफ तौर पर Lisp दुनिया की सीमाएँ आगे बढ़ा रही हैं, इसलिए support करने लायक लगा
उनका काम सचमुच कमाल का है। कभी-कभी Common Lisp इस्तेमाल करने वाले के तौर पर, ऐसा काम देखकर बहुत खुशी होती है
काश Nintendo या Sony जैसी कंपनियाँ ऐसे प्रयासों को सीधे support करें। आखिरकार यह console के लिए games, यानी IP बनाने का एक और तरीका ही है; platform level पर Github Accelerator जैसी चीज़ शुरू करने में क्या नुकसान होगा, समझ नहीं आता
Nintendo को Switch releases बढ़ाने के लिए कोई नया incentive बनाने की ज़रूरत नहीं है। उसके पास पहले से ही 14 करोड़ से ज्यादा units की बिक्री और high game purchase rate जैसा सबसे बड़ा incentive है
फिर भी अब प्रक्रियाएँ पहले जितनी ज्यादा नहीं रहीं। आजकल systems ज्यादातर general-purpose CPU और GPU architectures पर converge हो चुके हैं, और फर्क हों भी तो अक्सर छोटे decorative स्तर के होते हैं
इसलिए अच्छी चीज़ों का फायदा उठाना मुश्किल हो जाता है
ऐसे लेखों के लिए ही HN पर आता हूँ। मूल लेखक और उनके colleague को सलाम। पता है कि असंभव है, लेकिन अगर Nintendo अपने systems को थोड़ा और open तरीके से treat करे तो यह सचमुच बड़ी blessing होगी
कहीं भी समझाया नहीं गया, इसलिए जोड़ दूँ: SBCL का मतलब “Steel Bank Common Lisp” है
“Steel Bank Common Lisp(SBCL) एक high-performance Common Lisp compiler है। यह permissive license वाला open source/free software है, और ANSI Common Lisp के लिए compiler और runtime system के अलावा debugger, statistical profiler, code coverage tool, और कई extensions वाला interactive environment देता है”
https://www.sbcl.org/
https://benchmarksgame-team.pages.debian.net/benchmarksgame/box-plot-summary-charts.html