diff --git a/.gitignore b/.gitignore
index a1a87fa..f7679ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,12 @@
# VSCode file settings
.vscode/settings.json
-# Input elf files
+# Input elf and rom files
*.elf
+*.z64
# Output C files
-out/
+test/funcs
# Linux build output
build/
diff --git a/test/RecompTest.sln b/test/RecompTest.sln
new file mode 100644
index 0000000..76cae31
--- /dev/null
+++ b/test/RecompTest.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32929.386
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RecompTest", "RecompTest.vcxproj", "{73819ED8-8A5B-4554-B3F3-60257A43F296}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x64.ActiveCfg = Debug|x64
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x64.Build.0 = Debug|x64
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x86.ActiveCfg = Debug|Win32
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}.Debug|x86.Build.0 = Debug|Win32
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x64.ActiveCfg = Release|x64
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x64.Build.0 = Release|x64
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x86.ActiveCfg = Release|Win32
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DD740FE2-99F7-4C80-B124-06D14447F873}
+ EndGlobalSection
+EndGlobal
diff --git a/test/RecompTest.vcxproj b/test/RecompTest.vcxproj
new file mode 100644
index 0000000..638a52d
--- /dev/null
+++ b/test/RecompTest.vcxproj
@@ -0,0 +1,2028 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {73819ED8-8A5B-4554-B3F3-60257A43F296}
+ Win32Proj
+ 10.0
+
+
+
+ Application
+ true
+ v142
+
+
+ Application
+ false
+ v142
+
+
+ Application
+ true
+ v142
+
+
+ Application
+ false
+ v142
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreadedDebugDLL
+ Level3
+ ProgramDatabase
+ Disabled
+ $(ProjectDir)..;%(AdditionalIncludeDirectories)
+ stdcpp20
+
+
+ MachineX86
+ true
+ Console
+
+
+
+
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ MultiThreadedDLL
+ Level3
+ ProgramDatabase
+ $(ProjectDir)..;%(AdditionalIncludeDirectories)
+ stdcpp20
+
+
+ MachineX86
+ true
+ Console
+ true
+ true
+
+
+
+
+ $(ProjectDir)..;%(AdditionalIncludeDirectories)
+ stdcpp20
+
+
+ Console
+
+
+
+
+ $(ProjectDir)..;%(AdditionalIncludeDirectories)
+ stdcpp20
+
+
+ Console
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/RecompTest.vcxproj.filters b/test/RecompTest.vcxproj.filters
new file mode 100644
index 0000000..13821f7
--- /dev/null
+++ b/test/RecompTest.vcxproj.filters
@@ -0,0 +1,5718 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav
+
+
+ {686e650c-616a-4d1a-84c9-f967294cc727}
+
+
+
+
+ Source Files
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Funcs
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Funcs
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/test/portultra/init.cpp b/test/portultra/init.cpp
new file mode 100644
index 0000000..8151a7b
--- /dev/null
+++ b/test/portultra/init.cpp
@@ -0,0 +1,7 @@
+#include "ultra64.h"
+#include "multilibultra.hpp"
+
+extern "C" void osInitialize() {
+ Multilibultra::init_scheduler();
+ Multilibultra::native_init();
+}
diff --git a/test/portultra/main.c b/test/portultra/main.c
new file mode 100644
index 0000000..968b4d3
--- /dev/null
+++ b/test/portultra/main.c
@@ -0,0 +1,83 @@
+#if 0
+
+#include
+#include
+#include "ultra64.h"
+
+#define THREAD_STACK_SIZE 0x1000
+
+u8 idle_stack[THREAD_STACK_SIZE] ALIGNED(16);
+u8 main_stack[THREAD_STACK_SIZE] ALIGNED(16);
+u8 thread3_stack[THREAD_STACK_SIZE] ALIGNED(16);
+u8 thread4_stack[THREAD_STACK_SIZE] ALIGNED(16);
+
+OSThread idle_thread;
+OSThread main_thread;
+OSThread thread3;
+OSThread thread4;
+
+OSMesgQueue queue;
+OSMesg buf[1];
+
+void thread3_func(UNUSED void *arg) {
+ OSMesg val;
+ printf("Thread3 recv\n");
+ fflush(stdout);
+ osRecvMesg(&queue, &val, OS_MESG_BLOCK);
+ printf("Thread3 complete: %d\n", (int)(intptr_t)val);
+ fflush(stdout);
+}
+
+void thread4_func(void *arg) {
+ printf("Thread4 send %d\n", (int)(intptr_t)arg);
+ fflush(stdout);
+ osSendMesg(&queue, arg, OS_MESG_BLOCK);
+ printf("Thread4 complete\n");
+ fflush(stdout);
+}
+
+void main_thread_func(UNUSED void* arg) {
+ osCreateMesgQueue(&queue, buf, sizeof(buf) / sizeof(buf[0]));
+
+ printf("main thread creating thread 3\n");
+ osCreateThread(&thread3, 3, thread3_func, NULL, &thread3_stack[THREAD_STACK_SIZE], 14);
+ printf("main thread starting thread 3\n");
+ osStartThread(&thread3);
+
+ printf("main thread creating thread 4\n");
+ osCreateThread(&thread4, 4, thread4_func, (void*)10, &thread4_stack[THREAD_STACK_SIZE], 13);
+ printf("main thread starting thread 4\n");
+ osStartThread(&thread4);
+
+ while (1) {
+ printf("main thread doin stuff\n");
+ sleep(1);
+ }
+}
+
+void idle_thread_func(UNUSED void* arg) {
+ printf("idle thread\n");
+ printf("creating main thread\n");
+ osCreateThread(&main_thread, 2, main_thread_func, NULL, &main_stack[THREAD_STACK_SIZE], 11);
+ printf("starting main thread\n");
+ osStartThread(&main_thread);
+
+ // Set this thread's priority to 0, making it the idle thread
+ osSetThreadPri(NULL, 0);
+
+ // idle
+ while (1) {
+ printf("idle thread doin stuff\n");
+ sleep(1);
+ }
+}
+
+void bootproc(void) {
+ osInitialize();
+
+ osCreateThread(&idle_thread, 1, idle_thread_func, NULL, &idle_stack[THREAD_STACK_SIZE], 127);
+ printf("Starting idle thread\n");
+ osStartThread(&idle_thread);
+}
+
+#endif
diff --git a/test/portultra/mesgqueue.cpp b/test/portultra/mesgqueue.cpp
new file mode 100644
index 0000000..6326fb9
--- /dev/null
+++ b/test/portultra/mesgqueue.cpp
@@ -0,0 +1,188 @@
+#include
+#include
+
+#include "ultra64.h"
+#include "multilibultra.hpp"
+#include "recomp.h"
+
+extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) {
+ OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
+ mq->blocked_on_recv = NULLPTR;
+ mq->blocked_on_send = NULLPTR;
+ mq->msgCount = count;
+ mq->msg = msg;
+ mq->validCount = 0;
+ mq->first = 0;
+}
+
+s32 MQ_GET_COUNT(OSMesgQueue *mq) {
+ return mq->validCount;
+}
+
+s32 MQ_IS_EMPTY(OSMesgQueue *mq) {
+ return mq->validCount == 0;
+}
+
+s32 MQ_IS_FULL(OSMesgQueue* mq) {
+ return MQ_GET_COUNT(mq) >= mq->msgCount;
+}
+
+void thread_queue_insert(RDRAM_ARG PTR(OSThread)* queue, PTR(OSThread) toadd_) {
+ PTR(OSThread)* cur = queue;
+ OSThread* toadd = TO_PTR(OSThread, toadd_);
+ while (*cur && TO_PTR(OSThread, *cur)->priority > toadd->priority) {
+ cur = &TO_PTR(OSThread, *cur)->next;
+ }
+ toadd->next = (*cur);
+ *cur = toadd_;
+}
+
+OSThread* thread_queue_pop(RDRAM_ARG PTR(OSThread)* queue) {
+ PTR(OSThread) ret = *queue;
+ *queue = TO_PTR(OSThread, ret)->next;
+ return TO_PTR(OSThread, ret);
+}
+
+bool thread_queue_empty(RDRAM_ARG PTR(OSThread)* queue) {
+ return *queue == NULLPTR;
+}
+
+extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
+ OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
+ Multilibultra::disable_preemption();
+
+ if (flags == OS_MESG_NOBLOCK) {
+ // If non-blocking, fail if the queue is full
+ if (MQ_IS_FULL(mq)) {
+ Multilibultra::enable_preemption();
+ return -1;
+ }
+ } else {
+ // Otherwise, yield this thread until the queue has room
+ while (MQ_IS_FULL(mq)) {
+ debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
+ thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
+ Multilibultra::enable_preemption();
+ Multilibultra::pause_self(PASS_RDRAM1);
+ Multilibultra::disable_preemption();
+ }
+ }
+
+ s32 last = (mq->first + mq->validCount) % mq->msgCount;
+ TO_PTR(OSMesg, mq->msg)[last] = msg;
+ mq->validCount++;
+
+ OSThread* to_run = nullptr;
+
+ if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
+ to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
+ }
+
+ Multilibultra::enable_preemption();
+ if (to_run) {
+ debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
+ OSThread* self = TO_PTR(OSThread, Multilibultra::this_thread());
+ if (to_run->priority > self->priority) {
+ Multilibultra::swap_to_thread(PASS_RDRAM to_run);
+ } else {
+ Multilibultra::schedule_running_thread(to_run);
+ }
+ }
+ return 0;
+}
+
+extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) {
+ OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
+ Multilibultra::disable_preemption();
+
+ if (flags == OS_MESG_NOBLOCK) {
+ // If non-blocking, fail if the queue is full
+ if (MQ_IS_FULL(mq)) {
+ Multilibultra::enable_preemption();
+ return -1;
+ }
+ } else {
+ // Otherwise, yield this thread in a loop until the queue is no longer full
+ while (MQ_IS_FULL(mq)) {
+ debug_printf("[Message Queue] Thread %d is blocked on jam\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
+ thread_queue_insert(PASS_RDRAM &mq->blocked_on_send, Multilibultra::this_thread());
+ Multilibultra::enable_preemption();
+ Multilibultra::pause_self(PASS_RDRAM1);
+ Multilibultra::disable_preemption();
+ }
+ }
+
+ mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount;
+ TO_PTR(OSMesg, mq->msg)[mq->first] = msg;
+ mq->validCount++;
+
+ OSThread *to_run = nullptr;
+
+ if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_recv)) {
+ to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_recv);
+ }
+
+ Multilibultra::enable_preemption();
+ if (to_run) {
+ debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
+ OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
+ if (to_run->priority > self->priority) {
+ Multilibultra::swap_to_thread(PASS_RDRAM to_run);
+ } else {
+ Multilibultra::schedule_running_thread(to_run);
+ }
+ }
+ return 0;
+}
+
+extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) {
+ OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_);
+ OSMesg *msg = TO_PTR(OSMesg, msg_);
+ Multilibultra::disable_preemption();
+
+ if (flags == OS_MESG_NOBLOCK) {
+ // If non-blocking, fail if the queue is empty
+ if (MQ_IS_EMPTY(mq)) {
+ Multilibultra::enable_preemption();
+ return -1;
+ }
+ } else {
+ // Otherwise, yield this thread in a loop until the queue is no longer full
+ while (MQ_IS_EMPTY(mq)) {
+ debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, Multilibultra::this_thread())->id);
+ thread_queue_insert(PASS_RDRAM &mq->blocked_on_recv, Multilibultra::this_thread());
+ Multilibultra::enable_preemption();
+ Multilibultra::pause_self(PASS_RDRAM1);
+ Multilibultra::disable_preemption();
+ }
+ }
+
+ if (msg != nullptr) {
+ *msg = TO_PTR(OSMesg, mq->msg)[mq->first];
+ }
+
+ mq->first = (mq->first + 1) % mq->msgCount;
+ mq->validCount--;
+
+ OSThread *to_run = nullptr;
+
+ if (!thread_queue_empty(PASS_RDRAM &mq->blocked_on_send)) {
+ to_run = thread_queue_pop(PASS_RDRAM &mq->blocked_on_send);
+ }
+
+ Multilibultra::enable_preemption();
+ if (to_run) {
+ debug_printf("[Message Queue] Thread %d is unblocked\n", to_run->id);
+ OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
+ if (to_run->priority > self->priority) {
+ Multilibultra::swap_to_thread(PASS_RDRAM to_run);
+ } else {
+ Multilibultra::schedule_running_thread(to_run);
+ }
+ }
+ return 0;
+}
+
+extern "C" void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg) {
+
+}
diff --git a/test/portultra/multilibultra.hpp b/test/portultra/multilibultra.hpp
new file mode 100644
index 0000000..dc0bc9a
--- /dev/null
+++ b/test/portultra/multilibultra.hpp
@@ -0,0 +1,53 @@
+#ifndef __MULTILIBULTRA_HPP__
+#define __MULTILIBULTRA_HPP__
+
+#include
+#include
+#include
+
+#include "ultra64.h"
+#include "platform_specific.h"
+
+struct UltraThreadContext {
+ std::thread host_thread;
+ std::atomic_bool running;
+ std::atomic_bool initialized;
+};
+
+namespace Multilibultra {
+
+void native_init();
+void init_scheduler();
+void native_thread_init(OSThread *t);
+void set_self_paused(RDRAM_ARG1);
+void wait_for_resumed(RDRAM_ARG1);
+void swap_to_thread(RDRAM_ARG OSThread *to);
+void pause_thread_impl(OSThread *t);
+void pause_thread_native_impl(OSThread *t);
+void resume_thread_impl(OSThread *t);
+void resume_thread_native_impl(OSThread *t);
+void schedule_running_thread(OSThread *t);
+void stop_thread(OSThread *t);
+void pause_self(RDRAM_ARG1);
+void cleanup_thread(OSThread *t);
+PTR(OSThread) this_thread();
+void disable_preemption();
+void enable_preemption();
+void notify_scheduler();
+void reprioritize_thread(OSThread *t, OSPri pri);
+void set_main_thread();
+
+class preemption_guard {
+public:
+ preemption_guard();
+ ~preemption_guard();
+private:
+ std::lock_guard lock;
+};
+
+} // namespace Multilibultra
+
+#define debug_printf(...) printf(__VA_ARGS__);
+//#define debug_printf(...)
+
+#endif
diff --git a/test/portultra/platform_specific.h b/test/portultra/platform_specific.h
new file mode 100644
index 0000000..b9d1823
--- /dev/null
+++ b/test/portultra/platform_specific.h
@@ -0,0 +1,32 @@
+#ifndef __PLATFORM_SPECIFIC_H__
+#define __PLATFORM_SPECIFIC_H__
+
+#if defined(__linux__)
+
+//#include
+//
+//typedef struct {
+// pthread_t t;
+// pthread_barrier_t pause_barrier;
+// pthread_mutex_t pause_mutex;
+// pthread_cond_t pause_cond;
+// void (*entrypoint)(void *);
+// void *arg;
+//} OSThreadNative;
+
+#elif defined(_WIN32)
+
+//#include
+//
+//typedef struct {
+// pthread_t t;
+// pthread_barrier_t pause_barrier;
+// pthread_mutex_t pause_mutex;
+// pthread_cond_t pause_cond;
+// void (*entrypoint)(void*);
+// void* arg;
+//} OSThreadNative;
+
+#endif
+
+#endif
\ No newline at end of file
diff --git a/test/portultra/scheduler.cpp b/test/portultra/scheduler.cpp
new file mode 100644
index 0000000..52bb7d1
--- /dev/null
+++ b/test/portultra/scheduler.cpp
@@ -0,0 +1,268 @@
+#include
+#include
+#include
+#include
+
+#include "multilibultra.hpp"
+
+class OSThreadComparator {
+public:
+ bool operator() (OSThread *a, OSThread *b) const {
+ return a->priority < b->priority;
+ }
+};
+
+class thread_queue_t : public std::priority_queue, OSThreadComparator> {
+public:
+ // TODO comment this
+ bool remove(const OSThread* value) {
+ auto it = std::find(this->c.begin(), this->c.end(), value);
+
+ if (it == this->c.end()) {
+ return false;
+ }
+
+ if (it == this->c.begin()) {
+ // deque the top element
+ this->pop();
+ } else {
+ // remove element and re-heap
+ this->c.erase(it);
+ std::make_heap(this->c.begin(), this->c.end(), this->comp);
+ }
+
+ return true;
+ }
+};
+
+static struct {
+ std::vector to_schedule;
+ std::vector to_stop;
+ std::vector to_cleanup;
+ std::vector> to_reprioritize;
+ std::mutex mutex;
+ // OSThread* running_thread;
+ std::atomic_int notify_count;
+ std::atomic_int action_count;
+
+ bool can_preempt;
+ std::mutex premption_mutex;
+} scheduler_context{};
+
+void handle_thread_queueing(thread_queue_t& running_thread_queue) {
+ std::lock_guard lock{scheduler_context.mutex};
+
+ if (!scheduler_context.to_schedule.empty()) {
+ OSThread* to_schedule = scheduler_context.to_schedule.back();
+ scheduler_context.to_schedule.pop_back();
+ scheduler_context.action_count.fetch_sub(1);
+ debug_printf("[Scheduler] Scheduling thread %d\n", to_schedule->id);
+ running_thread_queue.push(to_schedule);
+ }
+}
+
+void handle_thread_stopping(thread_queue_t& running_thread_queue) {
+ std::lock_guard lock{scheduler_context.mutex};
+
+ while (!scheduler_context.to_stop.empty()) {
+ OSThread* to_stop = scheduler_context.to_stop.back();
+ scheduler_context.to_stop.pop_back();
+ scheduler_context.action_count.fetch_sub(1);
+ debug_printf("[Scheduler] Stopping thread %d\n", to_stop->id);
+ running_thread_queue.remove(to_stop);
+ }
+}
+
+void handle_thread_cleanup(thread_queue_t& running_thread_queue) {
+ std::lock_guard lock{scheduler_context.mutex};
+
+ while (!scheduler_context.to_cleanup.empty()) {
+ OSThread* to_cleanup = scheduler_context.to_cleanup.back();
+ scheduler_context.to_cleanup.pop_back();
+ scheduler_context.action_count.fetch_sub(1);
+
+ debug_printf("[Scheduler] Destroying thread %d\n", to_cleanup->id);
+ running_thread_queue.remove(to_cleanup);
+ to_cleanup->context->host_thread.join();
+ delete to_cleanup->context;
+ }
+}
+
+void handle_thread_reprioritization(thread_queue_t& running_thread_queue) {
+ std::lock_guard lock{scheduler_context.mutex};
+
+ while (!scheduler_context.to_reprioritize.empty()) {
+ const std::pair to_reprioritize = scheduler_context.to_reprioritize.back();
+ scheduler_context.to_reprioritize.pop_back();
+ scheduler_context.action_count.fetch_sub(1);
+
+ debug_printf("[Scheduler] Reprioritizing thread %d to %d\n", to_reprioritize.first->id, to_reprioritize.second);
+ running_thread_queue.remove(to_reprioritize.first);
+ to_reprioritize.first->priority = to_reprioritize.second;
+ running_thread_queue.push(to_reprioritize.first);
+ }
+}
+
+void handle_scheduler_notifications() {
+ std::lock_guard lock{scheduler_context.mutex};
+ int32_t notify_count = scheduler_context.notify_count.exchange(0);
+ if (notify_count) {
+ debug_printf("Received %d notifications\n", notify_count);
+ scheduler_context.action_count.fetch_sub(notify_count);
+ }
+}
+
+void swap_running_thread(thread_queue_t& running_thread_queue, OSThread*& cur_running_thread) {
+ OSThread* new_running_thread = running_thread_queue.top();
+ if (cur_running_thread != new_running_thread) {
+ if (cur_running_thread && cur_running_thread->state == OSThreadState::RUNNING) {
+ debug_printf("[Scheduler] Switching execution from thread %d (%d) to thread %d (%d)\n",
+ cur_running_thread->id, cur_running_thread->priority,
+ new_running_thread->id, new_running_thread->priority);
+ Multilibultra::pause_thread_impl(cur_running_thread);
+ } else {
+ debug_printf("[Scheduler] Switching execution to thread %d (%d)\n", new_running_thread->id, new_running_thread->priority);
+ }
+ Multilibultra::resume_thread_impl(new_running_thread);
+ cur_running_thread = new_running_thread;
+ } else if (cur_running_thread && cur_running_thread->state != OSThreadState::RUNNING) {
+ Multilibultra::resume_thread_impl(cur_running_thread);
+ }
+}
+
+void scheduler_func() {
+ thread_queue_t running_thread_queue{};
+ OSThread* cur_running_thread = nullptr;
+
+ while (true) {
+ OSThread* old_running_thread = cur_running_thread;
+ scheduler_context.action_count.wait(0);
+
+ std::lock_guard lock{scheduler_context.premption_mutex};
+
+ // Handle notifications
+ handle_scheduler_notifications();
+
+ // Handle stopping threads
+ handle_thread_stopping(running_thread_queue);
+
+ // Handle cleaning up threads
+ handle_thread_cleanup(running_thread_queue);
+
+ // Handle queueing threads to run
+ handle_thread_queueing(running_thread_queue);
+
+ // Handle threads that have changed priority
+ handle_thread_reprioritization(running_thread_queue);
+
+ // Determine which thread to run, stopping the current running thread if necessary
+ swap_running_thread(running_thread_queue, cur_running_thread);
+
+ std::this_thread::yield();
+ if (old_running_thread != cur_running_thread && old_running_thread && cur_running_thread) {
+ debug_printf("[Scheduler] Swapped from Thread %d (%d) to Thread %d (%d)\n",
+ old_running_thread->id, old_running_thread->priority, cur_running_thread->id, cur_running_thread->priority);
+ }
+ }
+}
+
+extern "C" void do_yield() {
+ std::this_thread::yield();
+}
+
+namespace Multilibultra {
+
+void init_scheduler() {
+ scheduler_context.can_preempt = true;
+ std::thread scheduler_thread{scheduler_func};
+ scheduler_thread.detach();
+}
+
+void schedule_running_thread(OSThread *t) {
+ debug_printf("[Scheduler] Queuing Thread %d to be scheduled\n", t->id);
+ std::lock_guard lock{scheduler_context.mutex};
+ scheduler_context.to_schedule.push_back(t);
+ scheduler_context.action_count.fetch_add(1);
+ scheduler_context.action_count.notify_all();
+}
+
+void swap_to_thread(RDRAM_ARG OSThread *to) {
+ OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
+ debug_printf("[Scheduler] Scheduling swap from thread %d to %d\n", self->id, to->id);
+ {
+ std::lock_guard lock{scheduler_context.mutex};
+ scheduler_context.to_schedule.push_back(to);
+ Multilibultra::set_self_paused(PASS_RDRAM1);
+ scheduler_context.action_count.fetch_add(1);
+ scheduler_context.action_count.notify_all();
+ }
+ Multilibultra::wait_for_resumed(PASS_RDRAM1);
+}
+
+void reprioritize_thread(OSThread *t, OSPri pri) {
+ debug_printf("[Scheduler] Adjusting Thread %d priority to %d\n", t->id, pri);
+ std::lock_guard lock{scheduler_context.mutex};
+ scheduler_context.to_reprioritize.emplace_back(t, pri);
+ scheduler_context.action_count.fetch_add(1);
+ scheduler_context.action_count.notify_all();
+}
+
+void pause_self(RDRAM_ARG1) {
+ OSThread *self = TO_PTR(OSThread, Multilibultra::this_thread());
+ debug_printf("[Scheduler] Thread %d pausing itself\n", self->id);
+ {
+ std::lock_guard lock{scheduler_context.mutex};
+ Multilibultra::set_self_paused(PASS_RDRAM1);
+ scheduler_context.to_stop.push_back(self);
+ scheduler_context.action_count.fetch_add(1);
+ scheduler_context.action_count.notify_all();
+ }
+ Multilibultra::wait_for_resumed(PASS_RDRAM1);
+}
+
+void stop_thread(OSThread *t) {
+ debug_printf("[Scheduler] Queuing Thread %d to be stopped\n", t->id);
+ {
+ std::lock_guard lock{scheduler_context.mutex};
+ scheduler_context.to_stop.push_back(t);
+ scheduler_context.action_count.fetch_add(1);
+ scheduler_context.action_count.notify_all();
+ }
+ Multilibultra::pause_thread_impl(t);
+}
+
+void cleanup_thread(OSThread *t) {
+ std::lock_guard lock{scheduler_context.mutex};
+ scheduler_context.to_cleanup.push_back(t);
+ scheduler_context.action_count.fetch_add(1);
+ scheduler_context.action_count.notify_all();
+}
+
+void disable_preemption() {
+ scheduler_context.premption_mutex.lock();
+ scheduler_context.can_preempt = false;
+}
+
+void enable_preemption() {
+ scheduler_context.can_preempt = true;
+ scheduler_context.premption_mutex.unlock();
+}
+
+// lock's constructor is called first, so can_preempt is set after locking
+preemption_guard::preemption_guard() : lock{scheduler_context.premption_mutex} {
+ scheduler_context.can_preempt = false;
+}
+
+// lock's destructor is called last, so can_preempt is set before unlocking
+preemption_guard::~preemption_guard() {
+ scheduler_context.can_preempt = true;
+}
+
+void notify_scheduler() {
+ std::lock_guard lock{scheduler_context.mutex};
+ scheduler_context.notify_count.fetch_add(1);
+ scheduler_context.action_count.fetch_add(1);
+ scheduler_context.action_count.notify_all();
+}
+
+}
diff --git a/test/portultra/task_pthreads.cpp b/test/portultra/task_pthreads.cpp
new file mode 100644
index 0000000..667f022
--- /dev/null
+++ b/test/portultra/task_pthreads.cpp
@@ -0,0 +1,60 @@
+#ifndef _WIN32
+
+// #include
+// #include
+// #include
+
+#include
+#include
+#include
+
+#include "ultra64.h"
+#include "multilibultra.hpp"
+
+constexpr int pause_thread_signum = SIGUSR1;
+
+// void cleanup_current_thread(OSThread *t) {
+// debug_printf("Thread cleanup %d\n", t->id);
+
+// // delete t->context;
+// }
+
+void sig_handler(int signum, siginfo_t *info, void *context) {
+ if (signum == pause_thread_signum) {
+ OSThread *t = Multilibultra::this_thread();
+
+ debug_printf("[Sig] Thread %d paused\n", t->id);
+
+ // Wait until the thread is marked as running again
+ t->context->running.wait(false);
+
+ debug_printf("[Sig] Thread %d resumed\n", t->id);
+ }
+}
+
+void Multilibultra::native_init(void) {
+ // Set up a signal handler to capture pause signals
+ struct sigaction sigact;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = SA_SIGINFO | SA_RESTART;
+ sigact.sa_sigaction = sig_handler;
+
+ sigaction(pause_thread_signum, &sigact, nullptr);
+}
+
+void Multilibultra::native_thread_init(OSThread *t) {
+ debug_printf("[Native] Init thread %d\n", t->id);
+}
+
+void Multilibultra::pause_thread_native_impl(OSThread *t) {
+ debug_printf("[Native] Pause thread %d\n", t->id);
+ // Send a pause signal to the thread, which will trigger it to wait on its pause barrier in the signal handler
+ pthread_kill(t->context->host_thread.native_handle(), pause_thread_signum);
+}
+
+void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) {
+ debug_printf("[Native] Resume thread %d\n", t->id);
+ // Nothing to do here
+}
+
+#endif
\ No newline at end of file
diff --git a/test/portultra/task_win32.cpp b/test/portultra/task_win32.cpp
new file mode 100644
index 0000000..833024d
--- /dev/null
+++ b/test/portultra/task_win32.cpp
@@ -0,0 +1,32 @@
+#include
+
+#include "ultra64.h"
+#include "multilibultra.hpp"
+
+extern "C" unsigned int sleep(unsigned int seconds) {
+ Sleep(seconds * 1000);
+ return 0;
+}
+
+void Multilibultra::native_init(void) {
+}
+
+void Multilibultra::native_thread_init(OSThread *t) {
+ debug_printf("[Native] Init thread %d\n", t->id);
+}
+
+void Multilibultra::pause_thread_native_impl(OSThread *t) {
+ debug_printf("[Native] Pause thread %d\n", t->id);
+ // Pause the thread via the win32 API
+ SuspendThread(t->context->host_thread.native_handle());
+ // Perform a synchronous action to ensure that the thread is suspended
+ // see: https://devblogs.microsoft.com/oldnewthing/20150205-00/?p=44743
+ CONTEXT threadContext;
+ GetThreadContext(t->context->host_thread.native_handle(), &threadContext);
+}
+
+void Multilibultra::resume_thread_native_impl(UNUSED OSThread *t) {
+ debug_printf("[Native] Resume thread %d\n", t->id);
+ // Resume the thread
+ ResumeThread(t->context->host_thread.native_handle());
+}
diff --git a/test/portultra/threads.cpp b/test/portultra/threads.cpp
new file mode 100644
index 0000000..63b45b6
--- /dev/null
+++ b/test/portultra/threads.cpp
@@ -0,0 +1,143 @@
+#include
+#include
+
+#include "ultra64.h"
+#include "multilibultra.hpp"
+
+extern "C" void bootproc();
+
+thread_local bool is_main_thread = false;
+thread_local PTR(OSThread) thread_self = NULLPTR;
+
+void Multilibultra::set_main_thread() {
+ is_main_thread = true;
+}
+
+#if 0
+int main(int argc, char** argv) {
+ Multilibultra::set_main_thread();
+
+ bootproc();
+}
+#endif
+
+#if 1
+void run_thread_function(uint8_t* rdram, uint32_t addr, uint32_t sp, uint32_t arg);
+#else
+#define run_thread_function(func, sp, arg) func(arg)
+#endif
+
+static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg) {
+ OSThread *self = TO_PTR(OSThread, self_);
+ debug_printf("[Thread] Thread created: %d\n", self->id);
+ thread_self = self_;
+
+ // Perform any necessary native thread initialization.
+ Multilibultra::native_thread_init(self);
+
+ // Set initialized to false to indicate that this thread can be started.
+ self->context->initialized.store(true);
+ self->context->initialized.notify_all();
+
+ debug_printf("[Thread] Thread waiting to be started: %d\n", self->id);
+
+ // Wait until the thread is marked as running.
+ Multilibultra::set_self_paused(PASS_RDRAM1);
+ Multilibultra::wait_for_resumed(PASS_RDRAM1);
+
+ debug_printf("[Thread] Thread started: %d\n", self->id);
+
+ // Run the thread's function with the provided argument.
+ run_thread_function(PASS_RDRAM entrypoint, self->sp, arg);
+
+ // Dispose of this thread after it completes.
+ Multilibultra::cleanup_thread(self);
+}
+
+extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t) {
+ debug_printf("[os] Start Thread %d\n", TO_PTR(OSThread, t)->id);
+
+ // Wait until the thread is initialized to indicate that it's ready to be started.
+ TO_PTR(OSThread, t)->context->initialized.wait(false);
+
+ debug_printf("[os] Thread %d is ready to be started\n", TO_PTR(OSThread, t)->id);
+
+ if (thread_self && (TO_PTR(OSThread, t)->priority > TO_PTR(OSThread, thread_self)->priority)) {
+ Multilibultra::swap_to_thread(PASS_RDRAM TO_PTR(OSThread, t));
+ } else {
+ Multilibultra::schedule_running_thread(TO_PTR(OSThread, t));
+ }
+
+ // The main thread "becomes" the first thread started, so join on it and exit after it completes.
+ if (is_main_thread) {
+ TO_PTR(OSThread, t)->context->host_thread.join();
+ std::exit(EXIT_SUCCESS);
+ }
+}
+
+extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) {
+ debug_printf("[os] Create Thread %d\n", id);
+ OSThread *t = TO_PTR(OSThread, t_);
+
+ t->next = NULLPTR;
+ t->priority = pri;
+ t->id = id;
+ t->state = OSThreadState::PAUSED;
+ t->sp = sp;
+
+ // Spawn a new thread, which will immediately pause itself and wait until it's been started.
+ t->context = new UltraThreadContext{};
+ t->context->initialized.store(false);
+ t->context->running.store(false);
+
+ t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg};
+}
+
+extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri) {
+ if (t == NULLPTR) {
+ t = thread_self;
+ }
+ bool pause_self = false;
+ if (pri > TO_PTR(OSThread, thread_self)->priority) {
+ pause_self = true;
+ Multilibultra::set_self_paused(PASS_RDRAM1);
+ } else if (t == thread_self && pri < TO_PTR(OSThread, thread_self)->priority) {
+ pause_self = true;
+ Multilibultra::set_self_paused(PASS_RDRAM1);
+ }
+ Multilibultra::reprioritize_thread(TO_PTR(OSThread, t), pri);
+ if (pause_self) {
+ Multilibultra::wait_for_resumed(PASS_RDRAM1);
+ }
+}
+
+// TODO yield thread, need a stable priority queue in the scheduler
+
+void Multilibultra::set_self_paused(RDRAM_ARG1) {
+ debug_printf("[Thread] Thread pausing itself: %d\n", TO_PTR(OSThread, thread_self)->id);
+ TO_PTR(OSThread, thread_self)->state = OSThreadState::PAUSED;
+ TO_PTR(OSThread, thread_self)->context->running.store(false);
+ TO_PTR(OSThread, thread_self)->context->running.notify_all();
+}
+
+void Multilibultra::wait_for_resumed(RDRAM_ARG1) {
+ TO_PTR(OSThread, thread_self)->context->running.wait(false);
+}
+
+void Multilibultra::pause_thread_impl(OSThread* t) {
+ t->state = OSThreadState::PREEMPTED;
+ t->context->running.store(false);
+ t->context->running.notify_all();
+ Multilibultra::pause_thread_native_impl(t);
+}
+
+void Multilibultra::resume_thread_impl(OSThread *t) {
+ t->state = OSThreadState::RUNNING;
+ t->context->running.store(true);
+ t->context->running.notify_all();
+ Multilibultra::resume_thread_native_impl(t);
+}
+
+PTR(OSThread) Multilibultra::this_thread() {
+ return thread_self;
+}
diff --git a/test/portultra/ultra64.h b/test/portultra/ultra64.h
new file mode 100644
index 0000000..fb444d5
--- /dev/null
+++ b/test/portultra/ultra64.h
@@ -0,0 +1,133 @@
+#ifndef __ULTRA64_MULTILIBULTRA_H__
+#define __ULTRA64_MULTILIBULTRA_H__
+
+#include
+#include "platform_specific.h"
+
+#ifdef __cplusplus
+#include
+#endif
+
+#ifdef __GNUC__
+#define UNUSED __attribute__((unused))
+#define ALIGNED(x) __attribute__((aligned(x)))
+#else
+#define UNUSED
+#define ALIGNED(x)
+#endif
+
+typedef int64_t s64;
+typedef uint64_t u64;
+typedef int32_t s32;
+typedef uint32_t u32;
+typedef int16_t s16;
+typedef uint16_t u16;
+typedef int8_t s8;
+typedef uint8_t u8;
+
+#define PTR(x) uint32_t
+#define RDRAM_ARG uint8_t *rdram,
+#define RDRAM_ARG1 uint8_t *rdram
+#define PASS_RDRAM rdram,
+#define PASS_RDRAM1 rdram
+#define TO_PTR(type, var) ((type*)(&rdram[var & 0x3FFFFFF]))
+#ifdef __cplusplus
+#define NULLPTR (PTR(void))0
+#endif
+
+#ifndef NULL
+#define NULL (PTR(void) 0)
+#endif
+
+#define OS_MESG_NOBLOCK 0
+#define OS_MESG_BLOCK 1
+
+typedef s32 OSPri;
+typedef s32 OSId;
+
+/////////////
+// Structs //
+/////////////
+
+typedef struct UltraThreadContext UltraThreadContext;
+
+typedef enum {
+ RUNNING,
+ PAUSED,
+ PREEMPTED
+} OSThreadState;
+
+typedef struct OSThread_t {
+ PTR(struct OSThread_t) next; // Next thread in the given queue
+ OSPri priority;
+ uint32_t pad1;
+ uint32_t pad2;
+ uint16_t flags; // These two are swapped to reflect rdram byteswapping
+ uint16_t state;
+ OSId id;
+ int32_t pad3;
+ UltraThreadContext* context; // An actual pointer regardless of platform
+ uint32_t sp;
+} OSThread;
+
+typedef u32 OSEvent;
+typedef PTR(void) OSMesg;
+
+// This union holds C++ members along with a padding array. Those members are guarded by an ifdef for C++
+// so that they don't cause compilation errors in C. The padding array reserves the necessary space to
+// hold the atomic members in C and a static assert is used to ensure that the union is large enough.
+// typedef union UltraQueueContext {
+// u64 pad[1];
+// #ifdef __cplusplus
+// struct {
+// } atomics;
+// // Construct pad instead of the atomics, which get constructed in-place in osCreateMesgQueue
+// UltraQueueContext() : pad{} {}
+// #endif
+// } UltraQueueContext;
+
+// #ifdef __cplusplus
+// static_assert(sizeof(UltraQueueContext::pad) == sizeof(UltraQueueContext),
+// "UltraQueueContext does not have enough padding to hold C++ members!");
+// #endif
+
+typedef struct OSMesgQueue {
+ PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */
+ PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */
+ s32 validCount; /* Number of messages in the queue */
+ s32 first; /* Index of the first message in the ring buffer */
+ s32 msgCount; /* Size of message buffer */
+ PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */
+} OSMesgQueue;
+
+///////////////
+// Functions //
+///////////////
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+void osInitialize(void);
+
+typedef void (thread_func_t)(PTR(void));
+
+void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry, PTR(void) arg, PTR(void) sp, OSPri p);
+void osStartThread(RDRAM_ARG PTR(OSThread) t);
+void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri);
+
+s32 MQ_GET_COUNT(RDRAM_ARG PTR(OSMesgQueue));
+s32 MQ_IS_EMPTY(RDRAM_ARG PTR(OSMesgQueue));
+s32 MQ_IS_FULL(RDRAM_ARG PTR(OSMesgQueue));
+
+void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
+s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
+s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32);
+s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32);
+void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif
diff --git a/test/src/ai.cpp b/test/src/ai.cpp
new file mode 100644
index 0000000..f3a8343
--- /dev/null
+++ b/test/src/ai.cpp
@@ -0,0 +1,17 @@
+#include "recomp.h"
+
+extern "C" void osAiSetFrequency_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osAiSetNextBuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osAiGetLength_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osAiGetStatus_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
\ No newline at end of file
diff --git a/test/src/cont.cpp b/test/src/cont.cpp
new file mode 100644
index 0000000..bf0d309
--- /dev/null
+++ b/test/src/cont.cpp
@@ -0,0 +1,25 @@
+#include "recomp.h"
+
+extern "C" void osContInit_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osContStartReadData_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osContGetReadData_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osMotorInit_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osMotorStart_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osMotorStop_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
diff --git a/test/src/dp.cpp b/test/src/dp.cpp
new file mode 100644
index 0000000..bb7bb1d
--- /dev/null
+++ b/test/src/dp.cpp
@@ -0,0 +1,5 @@
+#include "recomp.h"
+
+extern "C" void osDpSetNextBuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
diff --git a/test/src/eep.cpp b/test/src/eep.cpp
new file mode 100644
index 0000000..7c509da
--- /dev/null
+++ b/test/src/eep.cpp
@@ -0,0 +1,21 @@
+#include "recomp.h"
+
+extern "C" void osEepromProbe_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osEepromWrite_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osEepromLongWrite_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osEepromRead_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osEepromLongRead_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
diff --git a/test/src/misc_ultra.cpp b/test/src/misc_ultra.cpp
new file mode 100644
index 0000000..5115771
--- /dev/null
+++ b/test/src/misc_ultra.cpp
@@ -0,0 +1,65 @@
+#ifdef _WIN32
+#include
+#endif
+
+#include
+#include "recomp.h"
+
+extern uint64_t start_time;
+
+
+extern "C" void osVirtualToPhysical_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ uint32_t virtual_addr = ctx->r4;
+ // TODO handle TLB mappings
+ ctx->r2 = virtual_addr - 0x80000000;
+}
+
+extern "C" void osInvalDCache_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osInvalICache_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osWritebackDCache_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osWritebackDCacheAll_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+// Ticks per second
+constexpr uint32_t counter_rate = 46'875'000;
+
+extern "C" void osGetCount_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ // TODO move this to a more appropriate place
+ int32_t count = 0;
+#ifdef _WIN32
+ SYSTEMTIME st;
+ FILETIME ft;
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ft);
+
+ uint64_t cur_time = ((uint64_t)ft.dwHighDateTime << 32) + ft.dwLowDateTime;
+ uint64_t delta_100ns = cur_time - start_time;
+
+ count = (delta_100ns * counter_rate) / (1'000'000'000 / 100);
+#endif
+
+ ctx->r2 = count;
+ ;
+}
+
+extern "C" void osSetIntMask_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void __osDisableInt_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void __osRestoreInt_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
diff --git a/test/src/pi.cpp b/test/src/pi.cpp
new file mode 100644
index 0000000..65db5d9
--- /dev/null
+++ b/test/src/pi.cpp
@@ -0,0 +1,48 @@
+#include
+#include "recomp.h"
+#include "../portultra/ultra64.h"
+
+extern std::unique_ptr rom;
+extern size_t rom_size;
+
+extern "C" void osCartRomInit_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osCreatePiManager_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+constexpr uint32_t rom_base = 0xB0000000;
+
+extern "C" void osPiStartDma_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ uint32_t mb = ctx->r4;
+ uint32_t pri = ctx->r5;
+ uint32_t direction = ctx->r6;
+ uint32_t devAddr = ctx->r7;
+ uint32_t dramAddr = MEM_W(0x10, ctx->r29);
+ uint32_t size = MEM_W(0x14, ctx->r29);
+ uint32_t mq_ = MEM_W(0x18, ctx->r29);
+ OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_);
+
+ printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size);
+
+ // TODO asynchronous transfer (will require preemption in the scheduler)
+ // TODO this won't handle unaligned DMA
+ memcpy(rdram + (dramAddr & 0x3FFFFFF), rom.get() + (devAddr | rom_base) - rom_base, size);
+
+ // Send a message to the mq to indicate that the transfer completed
+ osSendMesg(rdram, mq_, 0, OS_MESG_NOBLOCK);
+}
+
+extern "C" void osEPiStartDma_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osPiGetStatus_recomp(uint8_t * restrict rdram, recomp_context * restrict ctx) {
+ ctx->r2 = 0;
+}
+
+extern "C" void osPiRawStartDma_recomp(uint8_t * restrict rdram, recomp_context * restrict ctx) {
+ ;
+}
\ No newline at end of file
diff --git a/test/src/portultra_translation.cpp b/test/src/portultra_translation.cpp
new file mode 100644
index 0000000..17d5fdb
--- /dev/null
+++ b/test/src/portultra_translation.cpp
@@ -0,0 +1,41 @@
+#include "../portultra/ultra64.h"
+#include "recomp.h"
+
+extern "C" void osInitialize_recomp(uint8_t * restrict rdram, recomp_context * restrict ctx) {
+ osInitialize();
+}
+
+extern "C" void osCreateThread_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ //printf("Creating thread 0x%08X\n", (uint32_t)ctx->r4);
+ osCreateThread(rdram, (uint32_t)ctx->r4, (OSId)ctx->r5, (uint32_t)ctx->r6, (uint32_t)ctx->r7,
+ (uint32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29));
+}
+
+extern "C" void osStartThread_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ //printf("Starting thread 0x%08X\n", (uint32_t)ctx->r4);
+ osStartThread(rdram, (uint32_t)ctx->r4);
+}
+
+extern "C" void osSetThreadPri_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ osSetThreadPri(rdram, (uint32_t)ctx->r4, (OSPri)ctx->r5);
+}
+
+extern "C" void osCreateMesgQueue_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ osCreateMesgQueue(rdram, (uint32_t)ctx->r4, (uint32_t)ctx->r5, (s32)ctx->r6);
+}
+
+extern "C" void osRecvMesg_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ctx->r2 = osRecvMesg(rdram, (uint32_t)ctx->r4, (uint32_t)ctx->r5, (s32)ctx->r6);
+}
+
+extern "C" void osSendMesg_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ctx->r2 = osSendMesg(rdram, (uint32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
+}
+
+extern "C" void osJamMesg_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ctx->r2 = osJamMesg(rdram, (uint32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6);
+}
+
+extern "C" void osSetEventMesg_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ osSetEventMesg(rdram, (OSEvent)ctx->r4, (uint32_t)ctx->r5, (OSMesg)ctx->r6);
+}
diff --git a/test/src/recomp.cpp b/test/src/recomp.cpp
new file mode 100644
index 0000000..2e5a61e
--- /dev/null
+++ b/test/src/recomp.cpp
@@ -0,0 +1,169 @@
+#ifdef _WIN32
+#include
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include "recomp.h"
+#include "../portultra/multilibultra.hpp"
+
+#ifdef _MSC_VER
+inline uint32_t byteswap(uint32_t val) {
+ return _byteswap_ulong(val);
+}
+#else
+constexpr uint32_t byteswap(uint32_t val) {
+ return __builtin_bswap32(val);
+}
+#endif
+
+void test_func(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ printf("in test_func\n");
+ exit(EXIT_FAILURE);
+}
+
+extern std::pair funcs[];
+extern const size_t num_funcs;
+
+std::unordered_map func_map{};
+
+extern "C" recomp_func_t* get_function(uint32_t addr) {
+ auto func_find = func_map.find(addr);
+ if (func_find == func_map.end()) {
+ fprintf(stderr, "Failed to find function at 0x%08X\n", addr);
+ std::exit(EXIT_FAILURE);
+ }
+ return func_find->second;
+}
+
+extern "C" void bzero(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ uint32_t start_addr = ctx->r4;
+ uint32_t size = ctx->r5;
+
+ for (uint32_t i = 0; i < size; i++) {
+ MEM_B(start_addr, i) = 0;
+ }
+}
+
+extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) {
+ printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl);
+ exit(EXIT_FAILURE);
+}
+
+extern "C" void do_break(uint32_t vram) {
+ printf("Encountered break at original vram 0x%08X\n", vram);
+ exit(EXIT_FAILURE);
+}
+
+void run_thread_function(uint8_t* rdram, uint32_t addr, uint32_t sp, uint32_t arg) {
+ recomp_context ctx{};
+ ctx.r29 = sp;
+ recomp_func_t* func = get_function(addr);
+ func(rdram, &ctx);
+}
+
+extern "C" void game_init(uint8_t* restrict rdram, recomp_context* restrict ctx);
+
+std::unique_ptr rom;
+size_t rom_size;
+
+uint64_t start_time;
+
+int main(int argc, char **argv) {
+ if (argc != 2) {
+ printf("Usage: %s [baserom]\n", argv[0]);
+ exit(EXIT_SUCCESS);
+ }
+
+ {
+ std::basic_ifstream rom_file{ argv[1], std::ios::binary };
+
+ size_t iobuf_size = 0x100000;
+ std::unique_ptr iobuf = std::make_unique(iobuf_size);
+ rom_file.rdbuf()->pubsetbuf(iobuf.get(), iobuf_size);
+
+ if (!rom_file) {
+ fprintf(stderr, "Failed to open rom: %s\n", argv[1]);
+ exit(EXIT_FAILURE);
+ }
+
+ rom_file.seekg(0, std::ios::end);
+ rom_size = rom_file.tellg();
+ rom_file.seekg(0, std::ios::beg);
+
+ rom = std::make_unique(rom_size);
+
+ rom_file.read(rom.get(), rom_size);
+ }
+
+ // Byteswap the rom
+ for (size_t rom_addr = 0; rom_addr < rom_size; rom_addr += 4) {
+ uint32_t word = *reinterpret_cast(rom.get() + rom_addr);
+ word = byteswap(word);
+ *reinterpret_cast(rom.get() + rom_addr) = word;
+ }
+
+ // Get entrypoint from ROM
+ // TODO fix this for other IPL3 versions
+ uint32_t entrypoint = *reinterpret_cast(rom.get() + 0x8);
+
+ // Allocate rdram_buffer
+ std::unique_ptr rdram_buffer = std::make_unique(8 * 1024 * 1024);
+ std::memset(rdram_buffer.get(), 0, 8 * 1024 * 1024);
+ recomp_context context{};
+
+ // Initial 1MB DMA
+ std::copy_n(rom.get() + 0x1000, 0x100000, rdram_buffer.get() + entrypoint - 0x80000000);
+
+ // Initialize function address map
+ for (size_t i = 0; i < num_funcs; i++) {
+ func_map[funcs[i].first] = funcs[i].second;
+ }
+
+ // TODO move this to a more appropriate place
+#ifdef _WIN32
+ {
+ SYSTEMTIME st;
+ FILETIME ft;
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ft);
+
+ start_time = ((uint64_t)ft.dwHighDateTime << 32) + ft.dwLowDateTime;
+ }
+#endif
+
+ // Set up stack pointer
+ context.r29 = 0x803FFFF0u;
+
+ // Initialize variables normally set by IPL3
+ constexpr uint32_t osTvType = 0x80000300;
+ constexpr uint32_t osRomType = 0x80000304;
+ constexpr uint32_t osRomBase = 0x80000308;
+ constexpr uint32_t osResetType = 0x8000030c;
+ constexpr uint32_t osCicId = 0x80000310;
+ constexpr uint32_t osVersion = 0x80000314;
+ constexpr uint32_t osMemSize = 0x80000318;
+ constexpr uint32_t osAppNMIBuffer = 0x8000031c;
+ uint8_t *rdram = rdram_buffer.get();
+ MEM_W(osTvType, 0) = 1; // NTSC
+ MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base
+ MEM_W(osResetType, 0) = 0; // cold reset
+ MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB
+
+ // Clear bss
+ // TODO run the entrypoint instead
+ memset(rdram_buffer.get() + 0XAF860, 0, 0xC00A0u - 0XAF860);
+
+ printf("[Recomp] Starting\n");
+
+ Multilibultra::set_main_thread();
+
+ game_init(rdram_buffer.get(), &context);
+
+ printf("[Recomp] Quitting\n");
+
+ return EXIT_SUCCESS;
+}
diff --git a/test/src/sp.cpp b/test/src/sp.cpp
new file mode 100644
index 0000000..b5b40b7
--- /dev/null
+++ b/test/src/sp.cpp
@@ -0,0 +1,22 @@
+#include
+#include "recomp.h"
+
+extern "C" void osSpTaskLoad_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osSpTaskStartGo_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4);
+}
+
+extern "C" void osSpTaskYield_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osSpTaskYielded_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void __osSpSetPc_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
diff --git a/test/src/vi.cpp b/test/src/vi.cpp
new file mode 100644
index 0000000..17c8921
--- /dev/null
+++ b/test/src/vi.cpp
@@ -0,0 +1,33 @@
+#include "recomp.h"
+
+extern "C" void osCreateViManager_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osViBlack_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osViSetSpecialFeatures_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osViGetNextFramebuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osViSwapBuffer_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osViSetMode_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}
+
+extern "C" void osViSetEvent_recomp(uint8_t* restrict rdram, recomp_context* restrict ctx) {
+ ;
+}