diff --git a/.gitignore b/.gitignore index c3b14e9af..96abdf505 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,10 @@ opencode.jsonc # config dirs .vscode/ .private-journal/ + +# GHCJS / browser build artifacts +dist-ghcjs/ +web/site/ + +# editor backups +*~ diff --git a/cabal-ghcjs-o1.project b/cabal-ghcjs-o1.project new file mode 100644 index 000000000..b851a5f04 --- /dev/null +++ b/cabal-ghcjs-o1.project @@ -0,0 +1,8 @@ +-- Release build for the GHC JavaScript backend: same as cabal-ghcjs.project +-- but optimised. Use with --builddir=dist-ghcjs-o1. +packages: . web + +with-compiler: javascript-unknown-ghcjs-ghc +with-hc-pkg: javascript-unknown-ghcjs-ghc-pkg + +optimization: 1 diff --git a/cabal-ghcjs.project b/cabal-ghcjs.project new file mode 100644 index 000000000..c9e985953 --- /dev/null +++ b/cabal-ghcjs.project @@ -0,0 +1,10 @@ +-- Build configuration for the GHC JavaScript backend (browser target). +-- Use with: cabal build --project-file=cabal-ghcjs.project --builddir=dist-ghcjs +packages: . web + +with-compiler: javascript-unknown-ghcjs-ghc +with-hc-pkg: javascript-unknown-ghcjs-ghc-pkg + +-- Faster iteration for the browser build; the compiler output is correctness +-- driven, not perf sensitive here. +optimization: 0 diff --git a/flake.nix b/flake.nix index 826e75154..e05def62f 100644 --- a/flake.nix +++ b/flake.nix @@ -22,6 +22,10 @@ inherit system; overlays = [ inputs.foundry.overlay ]; }; + # One GHC version across native and the JS backend: 9.10. Keeping the + # native compiler in step with the GHCJS cross-compiler avoids + # base/version divergence (e.g. foldl' in Prelude) and lets precompiled + # typecheck-cache dumps round-trip between the two. hspkgs = pkgs.haskell.packages.ghc910; gitignore = pkgs.nix-gitignore.gitignoreSourcePure [ ./.gitignore ]; @@ -83,7 +87,7 @@ src = gitignore ./.; } '' cd $src - ormolu --mode check $(find app src yule test -name '*.hs') + ormolu --mode check $(find app src yule test gen-std-cache -name '*.hs') touch $out ''; diff --git a/gen-std-cache/Main.hs b/gen-std-cache/Main.hs new file mode 100644 index 000000000..80469fc1b --- /dev/null +++ b/gen-std-cache/Main.hs @@ -0,0 +1,52 @@ +-- | Build tool: write the precompiled std typecheck-cache blob to a file. +-- +-- The browser IDE loads this blob on its first run (before IndexedDB is +-- populated) so it reuses std instead of retypechecking it. It is generated +-- natively — deterministic and free of the JS backend's synchronous-callback / +-- async-fs pitfalls — and is byte-identical to a blob dumped by the JS build, +-- because the content-hash cache keys and the serialized AST are toolchain +-- independent (pure keccak over a deterministic 'show'). +module Main where + +import Control.Monad.Except (runExceptT) +import Data.ByteString.Lazy qualified as BL +import Data.Map qualified as Map +import Solcore.Api (defaultOptions, dumpStdCacheBlob, indexCheckedByKey) +import Solcore.Frontend.Module.Loader (loadModuleGraphFromSource) +import Solcore.Pipeline.SolcorePipeline (compileDiagnosticsText, compileGraphWithCache) +import Solcore.Pipeline.TypecheckCache (moduleCacheKeys) +import System.Environment (getArgs) +import System.Exit (exitFailure) +import System.IO (hPutStrLn, stderr) + +-- Importing std and std.dispatch pulls the whole std closure (dispatch imports +-- opcodes); the trivial contract makes it a well-formed compile. +warmup :: String +warmup = + "import std.{*};\n" + ++ "import std.dispatch.{*};\n" + ++ "contract W { constructor() {} }\n" + +main :: IO () +main = do + args <- getArgs + case args of + [out] -> generate out + _ -> hPutStrLn stderr "usage: gen-std-cache " >> exitFailure + +generate :: FilePath -> IO () +generate out = do + graphE <- loadModuleGraphFromSource warmup + case graphE >>= \graph -> (,) graph <$> moduleCacheKeys defaultOptions graph of + Left err -> die ("gen-std-cache: " ++ err) + Right (graph, keys) -> do + res <- runExceptT (compileGraphWithCache defaultOptions graph Map.empty) + case res of + Left err -> die ("gen-std-cache: warm-up compile failed: " ++ compileDiagnosticsText err) + Right (_, checked) -> do + let blob = dumpStdCacheBlob (indexCheckedByKey keys checked) + BL.writeFile out blob + putStrLn ("gen-std-cache: wrote " ++ out ++ " (" ++ show (BL.length blob) ++ " bytes)") + +die :: String -> IO () +die msg = hPutStrLn stderr msg >> exitFailure diff --git a/ghcjs-flake.nix b/ghcjs-flake.nix new file mode 100644 index 000000000..0da52e6af --- /dev/null +++ b/ghcjs-flake.nix @@ -0,0 +1,210 @@ +{ + description = "sol-core"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + foundry = { + url = "github:shazow/foundry.nix/stable"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + goevmlab = { + url = "github:holiman/goevmlab"; + flake = false; + }; + }; + + outputs = inputs: + inputs.flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ inputs.foundry.overlay ]; + }; + # One GHC version across native and the JS backend: 9.10. Keeping the + # native compiler in step with the GHCJS cross-compiler avoids + # base/version divergence (e.g. foldl' in Prelude) and lets precompiled + # typecheck-cache dumps round-trip between the two. + hspkgs = pkgs.haskell.packages.ghc910; + + # The GHC JS backend cross-compiler, exposed on PATH as + # `javascript-unknown-ghcjs-ghc` (and `-ghc-pkg`). It is the same GHC + # 9.10 retargeted at the ghcjs platform, so it shares base/version with + # the native compiler above — a precondition for the browser build's + # precompiled typecheck-cache dumps to round-trip. Having it in this + # shell lets `web/build.sh` run alongside the native tools (solc, + # foundry, evmone) instead of from a separate ghcjs shell. + ghc-js = pkgs.haskell.compiler.ghc910.override { + stdenv = pkgs.stdenv.override { + targetPlatform = pkgs.pkgsCross.ghcjs.stdenv.targetPlatform; + }; + }; + + gitignore = pkgs.nix-gitignore.gitignoreSourcePure [ ./.gitignore ]; + sol-core = pkgs.haskell.lib.overrideCabal + (hspkgs.callCabal2nix "sol-core" (gitignore ./.) { }) + (_: { + # Keep package-level checks focused on unit tests. + # Contract tests run in checks.contests where evmone/testrunner are provisioned. + testTargets = [ "sol-core-tests" ]; + }); + sol-core-tests-no-warnings = pkgs.haskell.lib.overrideCabal sol-core + (old: { + buildTarget = "test:sol-core-tests"; + doHaddock = false; + enableLibraryProfiling = false; + checkPhase = '' + runHook preCheck + runHook postCheck + ''; + configureFlags = (old.configureFlags or []) ++ [ + "--ghc-options=-Werror" + ]; + }); + texlive = pkgs.texlive.combine { inherit (pkgs.texlive) scheme-small thmtools pdfsync lkproof cm-super; }; + evmone-lib = pkgs.callPackage ./nix/evmone.nix { }; + + testrunner = pkgs.stdenv.mkDerivation { + pname = "testrunner"; + version = "0.0"; + src = gitignore ./.; + + nativeBuildInputs = [ pkgs.cmake ]; + buildInputs = [ pkgs.boost pkgs.nlohmann_json ]; + + cmakeFlags = [ + "-DIGNORE_VENDORED_DEPENDENCIES=ON" + ]; + + installPhase = '' + mkdir -p $out/bin + cp test/testrunner/testrunner $out/bin/ + ''; + }; + in + rec { + packages.sol-core = sol-core; + packages.spec = pkgs.callPackage ./spec { solcoreTexlive = texlive; }; + packages.testrunner = testrunner; + packages.evmone = evmone-lib; + packages.tests-no-warnings = sol-core-tests-no-warnings; + packages.default = packages.sol-core; + + apps.sol-core = inputs.flake-utils.lib.mkApp { drv = packages.sol-core; }; + apps.default = apps.sol-core; + + checks = { + ormolu = pkgs.runCommand "ormolu-check" { + buildInputs = [ hspkgs.ormolu ]; + src = gitignore ./.; + } '' + cd $src + ormolu --mode check $(find app src yule test gen-std-cache -name '*.hs') + touch $out + ''; + + contests = pkgs.stdenv.mkDerivation { + pname = "solcore-contests"; + version = "0.0"; + src = gitignore ./.; + + nativeBuildInputs = [ pkgs.cmake ]; + buildInputs = [ + pkgs.boost + pkgs.nlohmann_json + sol-core + pkgs.solc + pkgs.jq + pkgs.coreutils + pkgs.bash + evmone-lib + ]; + + cmakeFlags = [ + "-DIGNORE_VENDORED_DEPENDENCIES=ON" + ]; + + # Build testrunner + buildPhase = '' + cmake --build . --target testrunner + ''; + + checkPhase = '' + cd .. + export PATH=${sol-core}/bin:${pkgs.solc}/bin:${pkgs.jq}/bin:$PATH + + # Override commands and paths to use Nix-provided binaries + export SOLCORE_CMD="sol-core" + export YULE_CMD="yule" + export testrunner_exe=build/test/testrunner/testrunner + if [[ -f "${evmone-lib}/lib/libevmone.so" ]]; then + export evmone=${evmone-lib}/lib/libevmone.so + elif [[ -f "${evmone-lib}/lib/libevmone.dylib" ]]; then + export evmone=${evmone-lib}/lib/libevmone.dylib + else + echo "libevmone shared library not found in ${evmone-lib}/lib" >&2 + exit 1 + fi + + # Run contest tests + bash run_contests.sh + ''; + + installPhase = '' + mkdir -p $out + echo "Contests passed" > $out/result + ''; + + doCheck = true; + }; + }; + + devShells.default = hspkgs.shellFor { + packages = _: [ sol-core ]; + buildInputs = [ + hspkgs.cabal-install + hspkgs.haskell-language-server + hspkgs.ormolu + ghc-js # JS backend cross-compiler for web/build.sh + pkgs.boost + pkgs.cmake + pkgs.foundry-bin + pkgs.go-ethereum + pkgs.jq + pkgs.nlohmann_json + pkgs.solc + evmone-lib + (hspkgs.hevm.overrideAttrs (old: { patches = []; })) + texlive + (pkgs.callPackage ./nix/goevmlab.nix { src = inputs.goevmlab; }) + pkgs.mdbook + ]; + evmone="${evmone-lib}/lib/${if pkgs.stdenv.isDarwin then "libevmone.dylib" else "libevmone.so"}"; + + # Make sure the C++ testrunner is (re)built whenever its sources + # change. CMake's incremental build is a no-op when nothing has + # changed, so this is cheap on warm shells. + # + # CMakeCache.txt is deleted before each configure so that cmake + # re-detects the compiler and make from the current nix store. + # The cache becomes stale when the shell enters a different + # derivation (different nix store hash for the same tool), causing + # "no such file or directory" errors when the old path is gone. + # Deleting the cache is safe: compiled object files are preserved + # so the subsequent build is still incremental. + shellHook = '' + if [ -z "''${SOLCORE_SKIP_TESTRUNNER_BUILD:-}" ]; then + testrunner_build_dir="''${PWD}/build" + echo "[nix develop] Configuring testrunner build in $testrunner_build_dir" + rm -f "$testrunner_build_dir/CMakeCache.txt" + cmake -S "$PWD" -B "$testrunner_build_dir" \ + -DIGNORE_VENDORED_DEPENDENCIES=ON >/dev/null + echo "[nix develop] Building testrunner (incremental)" + cmake --build "$testrunner_build_dir" --target testrunner + fi + ''; + }; + } + ); +} diff --git a/sol-core.cabal b/sol-core.cabal index 004a3b4d4..455fd696f 100644 --- a/sol-core.cabal +++ b/sol-core.cabal @@ -19,10 +19,9 @@ common common-opts build-depends: base >= 4.19.0.0 , mtl + , binary , bytestring , containers - , cryptonite - , memory , algebraic-graphs , array , directory @@ -60,6 +59,7 @@ library -- cabal-fmt: expand src exposed-modules: + Solcore.Api Solcore.Backend.ComptimeCheck Solcore.Backend.EmitHull Solcore.Backend.Mast @@ -116,7 +116,12 @@ library Solcore.Frontend.TypeInference.TcUnify Solcore.Pipeline.Options Solcore.Pipeline.SolcorePipeline + Solcore.Pipeline.TypecheckCache + Solcore.Pipeline.TcCacheSerialize Solcore.Primitives.Primitives + Solcore.Std.Bundle + Solcore.Std.Embed + Solcore.Util.Keccak Language.Hull Language.Hull.Compress Language.Hull.Parser @@ -153,6 +158,15 @@ executable sol-core ghc-options: -O1 -rtsopts +-- Build tool: dumps the precompiled std typecheck cache (web/site/std-cache.bin). +-- Native and deterministic; produces a blob byte-identical to the JS build's. +executable gen-std-cache + import: common-opts + main-is: Main.hs + hs-source-dirs: gen-std-cache + build-depends: sol-core + ghc-options: -O0 + executable yule import: common-opts main-is: Main.hs @@ -191,10 +205,13 @@ test-suite sol-core-tests DiagnosticCliTests DiagnosticTests HullCases + InMemoryApiTests + KeccakTests LocationTests MatchCompilerTests ModuleTypeCheckTests SpecialiseTests + TcCacheTests YulEvalTests ParserTests diff --git a/src/Solcore/Api.hs b/src/Solcore/Api.hs new file mode 100644 index 000000000..f5c81db81 --- /dev/null +++ b/src/Solcore/Api.hs @@ -0,0 +1,219 @@ +-- | In-memory compilation entry point. +-- +-- This is the seam the web IDE talks to: source text in (from an editor +-- buffer), structured result out (compiler output or diagnostics), with no +-- file-system access. It reuses the whole existing pipeline via +-- 'compileGraphWithCache', differing from the CLI only in how the module graph +-- is obtained — and in that it keeps a session-level typecheck cache so +-- repeated compiles (the edit-recompile loop) reuse unchanged modules. +module Solcore.Api + ( CompileResult (..), + compileSolcore, + entryContractAbis, + defaultOptions, + renderObjects, + -- Typecheck-cache persistence (Tier 2). The blob functions are pure and + -- testable; the 'IO' wrappers drive the session cache and marshal to a + -- Latin-1 string for the JS FFI / IndexedDB. + indexCheckedByKey, + dumpStdCacheBlob, + loadStdCacheBlob, + dumpStdCache, + loadStdCache, + ) +where + +import Control.Monad.Except (runExceptT) +import Data.ByteString.Lazy qualified as BL +import Data.Char (chr, ord) +import Data.IORef (IORef, modifyIORef', newIORef, readIORef) +import Data.List (intercalate) +import Data.Map (Map) +import Data.Map qualified as Map +import Language.Hull qualified as Hull +import Language.Hull.ToYul.Assemble (objectToYul) +import Solcore.Desugarer.ContractDispatch (contractAbiJson, nameStr) +import Solcore.Frontend.Module.Identity (LibraryId (StdLibrary), ModuleId (moduleLibrary)) +import Solcore.Frontend.Module.Identity qualified as Mod +import Solcore.Frontend.Module.Loader (ModuleGraph (..), loadModuleGraphFromSource) +import Solcore.Frontend.Pretty.SolcorePretty (pretty) +import Solcore.Frontend.Syntax.Contract (Contract (name), TopDecl (TContr)) +import Solcore.Frontend.TypeInference.TcModule (CheckedModule (..), moduleInferenceLocalDecls) +import Solcore.Pipeline.Options (Option, emptyOption) +import Solcore.Pipeline.SolcorePipeline (compileDiagnosticsText, compileGraphWithCache) +import Solcore.Pipeline.TcCacheSerialize (decodeCache, encodeCache, fromCachedModule, toCachedModule) +import Solcore.Pipeline.TypecheckCache (TcCacheKey, moduleCacheKeys) +import System.IO.Unsafe (unsafePerformIO) + +-- | Outcome of an in-memory compilation. +-- +-- * 'compileOutput' — pretty-printed Hull objects (the frontend result). +-- * 'compileYul' — generated Yul (the backend result), when the frontend +-- succeeded and Yul translation didn't fail. +-- * 'compileErrors' — diagnostics from whichever stage failed. +-- * 'compileCacheStatus' — per module (in dependency order), whether its +-- typecheck was reused from the session cache (@True@) or recomputed this +-- compile (@False@). Drives the UI's cache-hit indicator. +-- * 'compileAbis' — the JSON ABI of each contract defined in the entry module, +-- as @(contractName, abiJson)@. Always produced (the browser IDE emits ABIs +-- by default), mirroring the CLI's @--abi@ output without touching disk. +data CompileResult + = CompileResult + { compileOutput :: Maybe String, + compileYul :: Maybe String, + compileErrors :: [String], + compileCacheStatus :: [(String, Bool)], + compileAbis :: [(String, String)] + } + deriving (Eq, Show) + +-- | Session-level typecheck cache, keyed by content hash ('TcCacheKey'). The +-- GHCJS worker loads the compiler once and calls 'compileSolcore' repeatedly, +-- so this top-level ref persists across compiles within a session: the std +-- modules typechecked on the first compile are reused on every subsequent one, +-- while an edited module (whose key changes) is recomputed. Not persisted +-- across page reloads — that is a later tier (IndexedDB / embedded dump). +{-# NOINLINE tcCache #-} +tcCache :: IORef (Map TcCacheKey CheckedModule) +tcCache = unsafePerformIO (newIORef Map.empty) + +-- | Baseline options for the in-memory path. The file-system fields of +-- 'Option' (roots, output dir) are unused here; the UI is expected to override +-- the boolean pass toggles (checkboxes) and the partial-evaluation fuel (an +-- input field) on top of this. +defaultOptions :: Option +defaultOptions = emptyOption "Main.solc" + +-- | Compile a single solcore source module in memory. 'Option' is supplied by +-- the caller (the UI), so pass toggles and fuel are driven from the frontend. +compileSolcore :: Option -> String -> IO CompileResult +compileSolcore opts source = do + graphResult <- loadModuleGraphFromSource source + case graphResult of + Left err -> pure (CompileResult Nothing Nothing [err] [] []) + Right graph -> + case moduleCacheKeys opts graph of + Left err -> pure (CompileResult Nothing Nothing [err] [] []) + Right keys -> do + seed <- cacheSeed graph keys + let cacheStatus = + [ (Mod.moduleIdDisplay moduleId, Map.member moduleId seed) + | moduleId <- moduleOrder graph + ] + compiled <- runExceptT (compileGraphWithCache opts graph seed) + case compiled of + Left diags -> pure (CompileResult Nothing Nothing [compileDiagnosticsText diags] cacheStatus []) + Right (objs, checked) -> do + cacheCheckedModules keys checked + let hull = renderObjects objs + abis = entryContractAbis graph checked + yulResult <- objectsToYul objs + pure $ case yulResult of + Left err -> CompileResult (Just hull) Nothing [err] cacheStatus abis + Right yul -> CompileResult (Just hull) (Just yul) [] cacheStatus abis + +-- | The JSON ABI of every contract defined in the entry module, as +-- @(contractName, abiJson)@ — the same computation the CLI's @--abi@ performs, +-- reading the field-desugared local declarations the type checker already +-- prepared ('checkedModuleInput'). Safe to force even on a cache hit: the entry +-- module is the user's own source, so it is never one of the std modules served +-- from the reconstructed persisted cache (whose 'checkedModuleInput' is a thunk); +-- a session-cached entry stores the genuine 'CheckedModule'. +entryContractAbis :: ModuleGraph -> Map Mod.ModuleId CheckedModule -> [(String, String)] +entryContractAbis graph checked = + case Map.lookup (entryModule graph) checked of + Nothing -> [] + Just cm -> + [ (nameStr (name c), contractAbiJson c) + | TContr c <- moduleInferenceLocalDecls (checkedModuleInput cm) + ] + +-- | Build the per-compile reuse map: every module whose current key is already +-- in the session cache maps to its stored 'CheckedModule'. Modules whose source +-- (or an imported interface, or a relevant flag) changed have a new key and are +-- absent here, so the pipeline re-typechecks them. +cacheSeed :: + ModuleGraph -> + Map Mod.ModuleId TcCacheKey -> + IO (Map Mod.ModuleId CheckedModule) +cacheSeed graph keys = do + store <- readIORef tcCache + pure $ + Map.fromList + [ (moduleId, cached) + | moduleId <- moduleOrder graph, + Just key <- [Map.lookup moduleId keys], + Just cached <- [Map.lookup key store] + ] + +-- | Index freshly-checked modules by their cache key and merge them into the +-- session cache, so the next compile can reuse the ones whose key is unchanged. +cacheCheckedModules :: Map Mod.ModuleId TcCacheKey -> Map Mod.ModuleId CheckedModule -> IO () +cacheCheckedModules keys checked = + modifyIORef' tcCache (Map.union (indexCheckedByKey keys checked)) + +-- | Re-key checked modules by their content hash — the session-cache +-- representation ('TcCacheKey' is content-addressed, so this is what both the +-- in-memory cache and the persisted blob are keyed by). +indexCheckedByKey :: Map Mod.ModuleId TcCacheKey -> Map Mod.ModuleId CheckedModule -> Map TcCacheKey CheckedModule +indexCheckedByKey keys checked = + Map.fromList + [ (key, cm) + | (moduleId, cm) <- Map.toList checked, + Just key <- [Map.lookup moduleId keys] + ] + +-- | Serialize the std-library subset of a session cache to a blob. Only std +-- modules are persisted: they are the slow part, while the user's own modules +-- are cheap to recheck and change every keystroke. 'encodeCache' prefixes a +-- magic + version header, so a stale or foreign blob is rejected as a clean miss +-- on load rather than misread. +dumpStdCacheBlob :: Map TcCacheKey CheckedModule -> BL.ByteString +dumpStdCacheBlob session = + encodeCache (Map.map toCachedModule (Map.filter isStd session)) + where + isStd cm = moduleLibrary (checkedModuleId cm) == StdLibrary + +-- | Reconstruct session-cache entries from a persisted blob. A blob that fails +-- the header check decodes to an empty map (clean miss → recompute). The +-- 'Option' only feeds 'initTcEnv' for the reconstructed env; since a non-entry +-- module's env is read solely for its @typeTable@, the choice does not affect +-- results. +loadStdCacheBlob :: Option -> BL.ByteString -> Map TcCacheKey CheckedModule +loadStdCacheBlob opts blob = + case decodeCache blob of + Nothing -> Map.empty + Just cached -> Map.map (fromCachedModule opts) cached + +-- | Serialize the std subset of the live session cache as a Latin-1 string (one +-- byte per BMP code point, 0..255) for handing across the JS FFI to IndexedDB. +dumpStdCache :: IO String +dumpStdCache = blobToLatin1 . dumpStdCacheBlob <$> readIORef tcCache + +-- | Merge a persisted std cache (Latin-1-encoded blob) into the live session +-- cache, returning the number of modules loaded (0 on a header mismatch or +-- empty blob). Reconstructs with 'defaultOptions' — see 'loadStdCacheBlob'. +loadStdCache :: String -> IO Int +loadStdCache s = do + let recovered = loadStdCacheBlob defaultOptions (latin1ToBlob s) + modifyIORef' tcCache (Map.union recovered) + pure (Map.size recovered) + +-- | Byte string ⇄ Latin-1 string: each byte is one code point in 0..255, all +-- below the surrogate range, so 'toJSString'/'fromJSString' round-trip it +-- faithfully. +blobToLatin1 :: BL.ByteString -> String +blobToLatin1 = map (chr . fromIntegral) . BL.unpack + +latin1ToBlob :: String -> BL.ByteString +latin1ToBlob = BL.pack . map (fromIntegral . ord) + +renderObjects :: [Hull.Object] -> String +renderObjects = unlines . map pretty + +-- | Translate all hull objects to Yul, concatenating them. Fails with the first +-- Hull/Yul type error encountered. +objectsToYul :: [Hull.Object] -> IO (Either String String) +objectsToYul objs = do + results <- mapM objectToYul objs + pure (intercalate "\n\n" <$> sequence results) diff --git a/src/Solcore/Backend/MastEval.hs b/src/Solcore/Backend/MastEval.hs index 25854d6aa..2df799221 100644 --- a/src/Solcore/Backend/MastEval.hs +++ b/src/Solcore/Backend/MastEval.hs @@ -35,11 +35,9 @@ where import Control.Monad.Reader import Control.Monad.State -import Crypto.Hash (Digest, hash) -import Crypto.Hash.Algorithms (Keccak_256) import Data.Bits (complement, shiftL, shiftR, xor, (.&.), (.|.)) -import Data.ByteArray qualified as BA import Data.ByteString qualified as BS +import Data.List qualified as L import Data.Map.Strict qualified as Map import Data.Set qualified as Set import Data.Text qualified as T @@ -51,6 +49,7 @@ import Solcore.Backend.Mast import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.Stmt (Literal (..)) import Solcore.Primitives.Primitives (integerPrimNames) +import Solcore.Util.Keccak (keccak256) ----------------------------------------------------------------------- -- Data structures @@ -448,10 +447,7 @@ evalPrimitive (Name "strlenLit") [MastLit (StrLit s)] = in Just (MastLit (IntLit (toInteger (BS.length bs)))) evalPrimitive (Name "keccakLit") [MastLit (StrLit s)] = let bs = TE.encodeUtf8 (T.pack s) - digest :: Digest Keccak_256 - digest = hash bs - digestBytes :: BS.ByteString - digestBytes = BA.convert digest + digestBytes = keccak256 bs in Just (MastLit (IntLit (bsToIntegerBE digestBytes))) -- Integer (comptime-only, unlimited precision) primitives: evalPrimitive (Name "wordToInteger") [MastLit (IntLit n)] = @@ -504,7 +500,7 @@ maskWord n = n `mod` wordMod -- | Write a 256-bit value to memory at byte address p (big-endian, 32 bytes). mstoreBytes :: Integer -> Integer -> Map.Map Integer Word8 -> Map.Map Integer Word8 mstoreBytes p v mem = - foldl' + L.foldl' (\m i -> Map.insert (p + i) (fromIntegral ((v `shiftR` (8 * (31 - fromIntegral i))) .&. 0xff)) m) mem [0 .. 31] @@ -516,7 +512,7 @@ mstoreBytes p v mem = -- at slot 64 is set by initialization code before any user function runs). mloadWord :: Integer -> Map.Map Integer Word8 -> Maybe Integer mloadWord p mem = - foldl' + L.foldl' (\mAcc i -> do acc <- mAcc; b <- Map.lookup (p + i) mem; pure (acc * 256 + fromIntegral b)) (Just 0) [0 .. 31] diff --git a/src/Solcore/Desugarer/ContractDispatch.hs b/src/Solcore/Desugarer/ContractDispatch.hs index 483f95b47..377aadc69 100644 --- a/src/Solcore/Desugarer/ContractDispatch.hs +++ b/src/Solcore/Desugarer/ContractDispatch.hs @@ -13,6 +13,7 @@ module Solcore.Desugarer.ContractDispatch contractDispatchTopDecls, writeContractAbis, contractAbiJson, + nameStr, ) where diff --git a/src/Solcore/Frontend/Module/Loader.hs b/src/Solcore/Frontend/Module/Loader.hs index 033ad6c68..7c428b74f 100644 --- a/src/Solcore/Frontend/Module/Loader.hs +++ b/src/Solcore/Frontend/Module/Loader.hs @@ -3,6 +3,7 @@ module Solcore.Frontend.Module.Loader LoadedModule (..), ModuleTypeCheckSurface (..), loadModuleGraph, + loadModuleGraphFromSource, moduleSourceMap, moduleValidationTopDeclSegments, moduleSourcePath, @@ -25,9 +26,45 @@ import Solcore.Frontend.Module.Identity qualified as Mod import Solcore.Frontend.Parser.SolcoreParser (parseCompUnitWithPath) import Solcore.Frontend.Syntax.Name import Solcore.Frontend.Syntax.SyntaxTree +import Solcore.Std.Bundle (stdSources) import System.Directory (doesFileExist, makeAbsolute) import System.FilePath +-- | Pluggable source access for the loader. The real compiler backs this with +-- the file system; the in-memory / browser path backs it with a map of virtual +-- files. All of the loader's path-resolution logic is pure and works unchanged +-- over either. +data SourceFS + = SourceFS + { fsReadFile :: FilePath -> IO (Either String String), + fsDoesFileExist :: FilePath -> IO Bool, + fsMakeAbsolute :: FilePath -> IO FilePath + } + +-- | Source access backed by the real file system. +realSourceFS :: SourceFS +realSourceFS = + SourceFS + { fsReadFile = \path -> Right <$> readFile path, + fsDoesFileExist = doesFileExist, + fsMakeAbsolute = makeAbsolute + } + +-- | Source access backed by an in-memory map of @normalised path -> contents@. +-- Paths are normalised so lookups are insensitive to @.@/@//@ noise. +mapSourceFS :: Map FilePath String -> SourceFS +mapSourceFS files = + SourceFS + { fsReadFile = \path -> + pure $ + maybe + (Left ("in-memory source not found: " ++ path)) + Right + (Map.lookup (normalise path) files), + fsDoesFileExist = \path -> pure (Map.member (normalise path) files), + fsMakeAbsolute = pure . normalise + } + data LoadedModule = LoadedModule { loadedSourcePath :: FilePath, @@ -41,7 +78,8 @@ data LoaderConfig = LoaderConfig { mainRoot :: FilePath, stdRoot :: Maybe FilePath, - externalRoots :: Map Name FilePath + externalRoots :: Map Name FilePath, + loaderFS :: SourceFS } data LoadState @@ -89,9 +127,12 @@ data ModuleTypeCheckSurface deriving (Eq, Show) loadModuleGraph :: FilePath -> Maybe FilePath -> [(Name, FilePath)] -> FilePath -> IO (Either String ModuleGraph) -loadModuleGraph mainRootPath stdRootPath externalLibs entryFile = runExceptT do - entryAbsolute <- liftIO $ makeAbsolute entryFile - cfg <- liftIO $ mkLoaderConfig mainRootPath stdRootPath externalLibs entryFile +loadModuleGraph = loadModuleGraphWith realSourceFS + +loadModuleGraphWith :: SourceFS -> FilePath -> Maybe FilePath -> [(Name, FilePath)] -> FilePath -> IO (Either String ModuleGraph) +loadModuleGraphWith fs mainRootPath stdRootPath externalLibs entryFile = runExceptT do + entryAbsolute <- liftIO $ fsMakeAbsolute fs entryFile + cfg <- liftIO $ mkLoaderConfig fs mainRootPath stdRootPath externalLibs entryFile entryId <- moduleIdForPath Mod.MainLibrary (mainRoot cfg) entryAbsolute st <- execStateT (visit cfg entryId entryAbsolute) emptyLoadState let loaded = loadedModules st @@ -110,15 +151,37 @@ loadModuleGraph mainRootPath stdRootPath externalLibs entryFile = runExceptT do interfaces <- ExceptT $ pure (buildPublicInterfaceCache graph) pure graph {publicInterfaceCache = interfaces} -mkLoaderConfig :: FilePath -> Maybe FilePath -> [(Name, FilePath)] -> FilePath -> IO LoaderConfig -mkLoaderConfig mainRootPath stdRootPath externalLibs _entryFile = do - mainRoot' <- makeAbsolute mainRootPath - stdRoot' <- traverse makeAbsolute stdRootPath +-- | Build a module graph from editor source text with no file-system access. +-- +-- The source is mounted as @/Main.solc@ and the embedded standard library +-- under @/std@, so @import std;@ and friends resolve against the in-memory +-- bundle exactly as they would on disk. Simple contracts that don't touch std +-- (e.g. @00answer.solc@) compile without importing anything. +inMemoryMainPath :: FilePath +inMemoryMainPath = "/Main.solc" + +loadModuleGraphFromSource :: String -> IO (Either String ModuleGraph) +loadModuleGraphFromSource content = + loadModuleGraphWith fs mainRootPath (Just stdRootPath) [] inMemoryMainPath + where + mainRootPath = "/" + stdRootPath = "/std" + fs = mapSourceFS (Map.fromList (mainEntry : bundledStd)) + mainEntry = (normalise inMemoryMainPath, content) + bundledStd = + [ (normalise (stdRootPath stdFileName), source) + | (stdFileName, source) <- stdSources + ] + +mkLoaderConfig :: SourceFS -> FilePath -> Maybe FilePath -> [(Name, FilePath)] -> FilePath -> IO LoaderConfig +mkLoaderConfig fs mainRootPath stdRootPath externalLibs _entryFile = do + mainRoot' <- fsMakeAbsolute fs mainRootPath + stdRoot' <- traverse (fsMakeAbsolute fs) stdRootPath externalRoots' <- Map.fromList <$> mapM ( \(libName, libRoot) -> do - absRoot <- makeAbsolute libRoot + absRoot <- fsMakeAbsolute fs libRoot pure (libName, absRoot) ) externalLibs @@ -126,7 +189,8 @@ mkLoaderConfig mainRootPath stdRootPath externalLibs _entryFile = do LoaderConfig { mainRoot = mainRoot', stdRoot = stdRoot', - externalRoots = externalRoots' + externalRoots = externalRoots', + loaderFS = fs } visit :: @@ -139,7 +203,7 @@ visit cfg moduleId sourcePath = do loading <- gets (Set.member moduleId . loadingModules) unless (alreadyLoaded || loading) do modify (\st -> st {loadingModules = Set.insert moduleId (loadingModules st)}) - content <- liftIO (readFile sourcePath) + content <- either throwError pure =<< liftIO (fsReadFile (loaderFS cfg) sourcePath) let source = makeSourceFile sourcePath content parsed <- liftIO (parseCompUnitWithPath sourcePath content) cunit <- either throwError pure parsed @@ -190,7 +254,7 @@ resolveModuleReference cfg currentModule currentSourcePath refKind modulePath = (throwError . moduleReferenceDiagnostic ModuleReferenceMissingExternalRoot currentSourcePath refKind modulePath) pure (resolveModuleImportCandidates cfg currentModule modulePath) - resolved <- liftIO $ firstExisting candidates + resolved <- liftIO $ firstExisting (loaderFS cfg) candidates case resolved of Just (targetId, targetPath) -> pure (modulePath, targetId, targetPath) Nothing -> @@ -266,11 +330,11 @@ resolveModuleImportCandidates cfg currentModule path = let stdName = normalizeStdModuleName modName in (Mod.ModuleId Mod.StdLibrary stdName, toFilePath root stdName) -firstExisting :: [(Mod.ModuleId, FilePath)] -> IO (Maybe (Mod.ModuleId, FilePath)) -firstExisting [] = pure Nothing -firstExisting (candidate@(_, path) : rest) = do - exists <- doesFileExist path - if exists then pure (Just candidate) else firstExisting rest +firstExisting :: SourceFS -> [(Mod.ModuleId, FilePath)] -> IO (Maybe (Mod.ModuleId, FilePath)) +firstExisting _ [] = pure Nothing +firstExisting fs (candidate@(_, path) : rest) = do + exists <- fsDoesFileExist fs path + if exists then pure (Just candidate) else firstExisting fs rest isStdSpecial :: Name -> Bool isStdSpecial (Name "std") = True diff --git a/src/Solcore/Pipeline/SolcorePipeline.hs b/src/Solcore/Pipeline/SolcorePipeline.hs index 6a3046527..572827b66 100644 --- a/src/Solcore/Pipeline/SolcorePipeline.hs +++ b/src/Solcore/Pipeline/SolcorePipeline.hs @@ -106,19 +106,37 @@ compile opts = compileWithDiagnostics :: Option -> IO (Either CompileDiagnostics [Hull.Object]) compileWithDiagnostics opts = runExceptT $ do - let verbose = optVerbose opts - noMatchCompiler = optNoMatchCompiler opts - noIfDesugar = optNoIfDesugar opts - timeItNamed :: String -> IO a -> IO a - timeItNamed = optTimeItNamed opts - file = fileName opts + let file = fileName opts mainRoot <- liftIO $ makeAbsolute (optRootDir opts) stdRoot <- liftEitherDiagnostic emptySourceMap (parseStdRoot (optImportDirs opts)) externalLibs <- liftEitherDiagnostic emptySourceMap (parseExternalLibSpecs (optExternalLibs opts)) -- Parsing and import loading graph <- liftEitherDiagnosticIO emptySourceMap (loadModuleGraph mainRoot stdRoot externalLibs file) - let sources = moduleSourceMap graph + compileGraph opts graph + +-- Run the pipeline on an already-loaded module graph. The graph's origin (on +-- disk or built in memory from source text) is irrelevant here, so this is the +-- shared core used by both the file-based CLI and the in-memory API. +compileGraph :: Option -> ModuleGraph -> ExceptT CompileDiagnostics IO [Hull.Object] +compileGraph opts graph = fst <$> compileGraphWithCache opts graph Map.empty + +-- Cache-aware variant of 'compileGraph'. Modules present in the supplied cache +-- are reused verbatim instead of being re-typechecked; every other module is +-- typechecked as usual. The full set of checked modules (cached + freshly +-- checked) is returned alongside the hull so a caller can seed the next run. +compileGraphWithCache :: + Option -> + ModuleGraph -> + Map Mod.ModuleId CheckedModule -> + ExceptT CompileDiagnostics IO ([Hull.Object], Map Mod.ModuleId CheckedModule) +compileGraphWithCache opts graph cache = do + let verbose = optVerbose opts + noMatchCompiler = optNoMatchCompiler opts + noIfDesugar = optNoIfDesugar opts + timeItNamed :: String -> IO a -> IO a + timeItNamed = optTimeItNamed opts + sources = moduleSourceMap graph -- Validate each module against only its own direct imports. forM_ (moduleOrder graph) $ \moduleId -> do @@ -143,7 +161,7 @@ compileWithDiagnostics opts = runExceptT $ do liftCompilerDiagnosticIO sources ( timeItNamed "Typecheck modules" $ - runExceptT (typeCheckLoadedModules opts graph) + runExceptT (typeCheckLoadedModulesWithCache opts graph cache) ) checkedAssembly <- liftEitherDiagnostic sources (assembleCheckedModules graph checkedModules) let typed = checkedAssemblyCompUnit checkedAssembly @@ -180,7 +198,7 @@ compileWithDiagnostics opts = runExceptT $ do -- Specialization & Hull Generation if optNoSpec opts - then pure [] + then pure ([], checkedModules) else do specialized <- liftIO $ @@ -224,7 +242,7 @@ compileWithDiagnostics opts = runExceptT $ do putStrLn "> Hull contract(s):" forM_ hull (putStrLn . pretty) - pure hull + pure (hull, checkedModules) renderCompileDiagnostics :: Option -> CompileDiagnostics -> String renderCompileDiagnostics opts diagnostics = @@ -714,7 +732,22 @@ ensureSourcePath sources path typeCheckLoadedModules :: Option -> ModuleGraph -> ExceptT CompilerError IO (Map Mod.ModuleId CheckedModule) typeCheckLoadedModules opts graph = - Map.fromList <$> mapM (typeCheckModuleFromGraph opts graph) (moduleOrder graph) + typeCheckLoadedModulesWithCache opts graph Map.empty + +-- Typecheck every module in graph order, reusing any module already present in +-- the cache instead of re-checking it. +typeCheckLoadedModulesWithCache :: + Option -> + ModuleGraph -> + Map Mod.ModuleId CheckedModule -> + ExceptT CompilerError IO (Map Mod.ModuleId CheckedModule) +typeCheckLoadedModulesWithCache opts graph cache = + Map.fromList <$> mapM checkOrReuse (moduleOrder graph) + where + checkOrReuse moduleId = + case Map.lookup moduleId cache of + Just checkedModule -> pure (moduleId, checkedModule) + Nothing -> typeCheckModuleFromGraph opts graph moduleId typeCheckModuleFromGraph :: Option -> diff --git a/src/Solcore/Pipeline/TcCacheSerialize.hs b/src/Solcore/Pipeline/TcCacheSerialize.hs new file mode 100644 index 000000000..e20a2652f --- /dev/null +++ b/src/Solcore/Pipeline/TcCacheSerialize.hs @@ -0,0 +1,333 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | Serialization for the typecheck cache (Phase 3). +-- +-- All @Generic@/@Binary@ instances live here as standalone deriving, so the +-- @Syntax.*@ modules stay untouched. We use @binary@ (a GHC boot library, pure +-- Haskell, no dependency tail) so the same code cross-compiles to the JS +-- backend; the format is confined behind 'encodeCache' / 'decodeCache' and can +-- be swapped without touching the rest of the pipeline. +-- +-- A cached module carries only what the assembly step actually reads back from a +-- non-entry module: its typed 'CompUnit' and the @typeTable@ of its 'TcEnv' +-- (see 'mergeCheckedModuleEnvs', which takes every other env field from the +-- never-cached entry module). The dropped 'CheckedModule' fields are restored as +-- loud error thunks: proven unread, so never forced, but failing clearly if that +-- assumption is ever violated. +module Solcore.Pipeline.TcCacheSerialize + ( CachedModule (..), + toCachedModule, + fromCachedModule, + encodeCache, + decodeCache, + ) +where + +import Data.Binary (Binary, get, put) +import Data.Binary.Get (getWord8, runGetOrFail) +import Data.Binary.Put (putWord8, runPut) +import Data.ByteString.Lazy qualified as BL +import Data.Map (Map) +import Data.Word (Word32) +import GHC.Generics (Generic) +import Language.Yul (YLiteral (..), YulExp (..), YulStmt (..)) +import Solcore.Frontend.Module.Identity +import Solcore.Frontend.Syntax.Contract +import Solcore.Frontend.Syntax.Location (NodeLocation, generatedNode) +import Solcore.Frontend.Syntax.Name +import Solcore.Frontend.Syntax.Stmt +import Solcore.Frontend.Syntax.Ty +import Solcore.Frontend.TypeInference.Id +import Solcore.Frontend.TypeInference.TcEnv (TcEnv (..), TypeInfo (..), initTcEnv) +import Solcore.Frontend.TypeInference.TcModule (CheckedModule (..)) +import Solcore.Pipeline.Options (Option) +import Solcore.Pipeline.TypecheckCache (TcCacheKey (..)) + +-- Names / identifiers ------------------------------------------------------- + +-- A 'Name' carries an optional source span that its 'Eq'/'Ord' ignore (they +-- compare only the name segments). Serialize just the segment structure and drop +-- the span, for the same toolchain-independence reason as 'NodeLocation' below; +-- reconstruct with the span-less 'Name'/'QualName' patterns. +instance Binary Name where + put (Name s) = putWord8 0 >> put s + put (QualName qualifier s) = putWord8 1 >> put qualifier >> put s + get = do + tag <- getWord8 + case tag of + 0 -> Name <$> get + 1 -> QualName <$> get <*> get + _ -> fail ("Binary Name: unknown constructor tag " ++ show tag) + +deriving stock instance Generic Id + +deriving anyclass instance Binary Id + +deriving stock instance Generic LibraryId + +deriving anyclass instance Binary LibraryId + +deriving stock instance Generic ModuleId + +deriving anyclass instance Binary ModuleId + +-- Types --------------------------------------------------------------------- +deriving stock instance Generic Tyvar + +deriving anyclass instance Binary Tyvar + +deriving stock instance Generic Ty + +deriving anyclass instance Binary Ty + +deriving stock instance Generic MetaTv + +deriving anyclass instance Binary MetaTv + +deriving stock instance Generic Pred + +deriving anyclass instance Binary Pred + +deriving stock instance Generic (Qual t) + +deriving anyclass instance (Binary t) => Binary (Qual t) + +deriving stock instance Generic Scheme + +deriving anyclass instance Binary Scheme + +deriving stock instance Generic TypeInfo + +deriving anyclass instance Binary TypeInfo + +-- Top level ----------------------------------------------------------------- +deriving stock instance Generic (CompUnit a) + +deriving anyclass instance (Binary a) => Binary (CompUnit a) + +deriving stock instance Generic (TopDecl a) + +deriving anyclass instance (Binary a) => Binary (TopDecl a) + +deriving stock instance Generic (Contract a) + +deriving anyclass instance (Binary a) => Binary (Contract a) + +deriving stock instance Generic (ContractDecl a) + +deriving anyclass instance (Binary a) => Binary (ContractDecl a) + +deriving stock instance Generic (Field a) + +deriving anyclass instance (Binary a) => Binary (Field a) + +deriving stock instance Generic (Constructor a) + +deriving anyclass instance (Binary a) => Binary (Constructor a) + +deriving stock instance Generic (FunDef a) + +deriving anyclass instance (Binary a) => Binary (FunDef a) + +deriving stock instance Generic (Signature a) + +deriving anyclass instance (Binary a) => Binary (Signature a) + +deriving stock instance Generic (Class a) + +deriving anyclass instance (Binary a) => Binary (Class a) + +deriving stock instance Generic (Instance a) + +deriving anyclass instance (Binary a) => Binary (Instance a) + +deriving stock instance Generic DataTy + +deriving anyclass instance Binary DataTy + +deriving stock instance Generic Constr + +deriving anyclass instance Binary Constr + +deriving stock instance Generic TySym + +deriving anyclass instance Binary TySym + +deriving stock instance Generic TopDeclKey + +deriving anyclass instance Binary TopDeclKey + +-- Modules / imports / exports ---------------------------------------------- +deriving stock instance Generic Import + +deriving anyclass instance Binary Import + +deriving stock instance Generic ModulePath + +deriving anyclass instance Binary ModulePath + +deriving stock instance Generic Export + +deriving anyclass instance Binary Export + +deriving stock instance Generic ExportSpec + +deriving anyclass instance Binary ExportSpec + +deriving stock instance Generic ExportSelector + +deriving anyclass instance Binary ExportSelector + +deriving stock instance Generic ExportSelectorEntry + +deriving anyclass instance Binary ExportSelectorEntry + +deriving stock instance Generic ConstructorSelector + +deriving anyclass instance Binary ConstructorSelector + +deriving stock instance Generic ItemSelector + +deriving anyclass instance Binary ItemSelector + +deriving stock instance Generic ItemSelectorEntry + +deriving anyclass instance Binary ItemSelectorEntry + +-- Pragmas ------------------------------------------------------------------- +deriving stock instance Generic Pragma + +deriving anyclass instance Binary Pragma + +deriving stock instance Generic PragmaType + +deriving anyclass instance Binary PragmaType + +deriving stock instance Generic PragmaStatus + +deriving anyclass instance Binary PragmaStatus + +-- Statements / expressions / patterns -------------------------------------- + +-- A 'NodeLocation' is diagnostic metadata, not part of a module's typechecked +-- meaning (its 'Eq'/'Ord' are total), and its source span carries a +-- build-specific file path. Persisting spans would make the cache blob depend on +-- where std was compiled, breaking the content-addressed cache's toolchain +-- independence: the native gen-std-cache blob must be byte-identical to what the +-- browser would dump. Serialize every location as the generated (span-less) node +-- so cached ASTs round-trip deterministically; cached modules typecheck cleanly, +-- so the lost spans never surface in a diagnostic. +instance Binary NodeLocation where + put _ = pure () + get = pure generatedNode + +deriving stock instance Generic (Stmt a) + +deriving anyclass instance (Binary a) => Binary (Stmt a) + +deriving stock instance Generic (Param a) + +deriving anyclass instance (Binary a) => Binary (Param a) + +deriving stock instance Generic (Exp a) + +deriving anyclass instance (Binary a) => Binary (Exp a) + +deriving stock instance Generic (Pat a) + +deriving anyclass instance (Binary a) => Binary (Pat a) + +deriving stock instance Generic Literal + +deriving anyclass instance Binary Literal + +-- Inline Yul (reachable from the typed AST via @Asm YulBlock@) --------------- +deriving stock instance Generic YulStmt + +deriving anyclass instance Binary YulStmt + +deriving stock instance Generic YulExp + +deriving anyclass instance Binary YulExp + +deriving stock instance Generic YLiteral + +deriving anyclass instance Binary YLiteral + +-- Cache key ----------------------------------------------------------------- +deriving stock instance Generic TcCacheKey + +deriving anyclass instance Binary TcCacheKey + +-- Payload ------------------------------------------------------------------- + +-- | The serialized form of a typechecked module: exactly what the assembly step +-- reads back from a non-entry module. +data CachedModule + = CachedModule + { cachedModuleId :: ModuleId, + cachedTyped :: CompUnit Id, + cachedTypeTable :: Map Name TypeInfo + } + deriving stock (Generic) + deriving anyclass (Binary) + +toCachedModule :: CheckedModule -> CachedModule +toCachedModule cm = + CachedModule + { cachedModuleId = checkedModuleId cm, + cachedTyped = checkedModuleTyped cm, + cachedTypeTable = typeTable (checkedModuleEnv cm) + } + +-- | Rebuild a 'CheckedModule' from its cached payload. The env is @initTcEnv@ +-- with the cached @typeTable@ spliced in — the only env field read back from a +-- non-entry module. The one unread field is a loud error thunk. +fromCachedModule :: Option -> CachedModule -> CheckedModule +fromCachedModule opts cached = + CheckedModule + { checkedModuleId = cachedModuleId cached, + checkedModuleTyped = cachedTyped cached, + checkedModuleEnv = (initTcEnv opts) {typeTable = cachedTypeTable cached}, + checkedModuleInput = error "tc-cache: checkedModuleInput not restored from cache" + } + +-- | Magic number identifying a typecheck-cache blob ("STC" ++ 0x01). +tcCacheMagic :: Word32 +tcCacheMagic = 0x53544301 + +-- | Format version. BUMP THIS whenever any serialized type changes shape (a +-- constructor or field added, removed, or reordered), so a dump written by an +-- incompatible build is rejected rather than misread. A rejected dump degrades +-- to a cache miss (recompute) — never a wrong result. +tcCacheFormatVersion :: Word32 +tcCacheFormatVersion = 1 + +-- | Encode the keyed cache behind a magic + version header. +encodeCache :: Map TcCacheKey CachedModule -> BL.ByteString +encodeCache cache = + runPut (put tcCacheMagic >> put tcCacheFormatVersion >> put cache) + +-- | Decode a cache blob, returning 'Nothing' if the header is absent or does +-- not match (foreign format, wrong version, or corruption). The payload is only +-- parsed once the header matches, so a version mismatch can never crash while +-- decoding stale bytes. +decodeCache :: BL.ByteString -> Maybe (Map TcCacheKey CachedModule) +decodeCache blob = + case runGetOrFail getChecked blob of + Right (_, _, result) -> result + Left _ -> Nothing + where + getChecked = do + magic <- get + version <- get + if magic == tcCacheMagic && version == tcCacheFormatVersion + then Just <$> get + else pure Nothing diff --git a/src/Solcore/Pipeline/TypecheckCache.hs b/src/Solcore/Pipeline/TypecheckCache.hs new file mode 100644 index 000000000..66f7ecb8b --- /dev/null +++ b/src/Solcore/Pipeline/TypecheckCache.hs @@ -0,0 +1,126 @@ +-- | Merkle cache keys for the typecheck cache (Phase 2). +-- +-- A module's typechecked output is a pure function of its own source, the +-- public interfaces of the modules it references, and the compile flags that +-- reach the typecheck phase. We capture that as a content-addressed key: +-- +-- @ +-- key(M) = H( contentHash(M) +-- ++ sorted [ key(R) | R <- references(M) ] +-- ++ flagComponent(M) ) +-- @ +-- +-- folded over the graph in dependency order. Two modules with equal keys are +-- guaranteed to typecheck to the same result, so a cache indexed by key is +-- sound; editing a module changes its key and, transitively, the keys of every +-- module that references it — and nothing else. +-- +-- The content hash is taken over the /parsed/ compilation unit (which the graph +-- already holds), so it is insensitive to comments and whitespace for free. +module Solcore.Pipeline.TypecheckCache + ( TcCacheKey (..), + moduleHasContracts, + moduleCacheKeys, + moduleCacheKeysWith, + transitiveDependents, + ) +where + +import Data.ByteString (ByteString) +import Data.ByteString qualified as BS +import Data.ByteString.Char8 qualified as BC +import Data.List (sort) +import Data.Map (Map) +import Data.Map qualified as Map +import Data.Set (Set) +import Data.Set qualified as Set +import Solcore.Frontend.Module.Identity qualified as Mod +import Solcore.Frontend.Module.Loader +import Solcore.Frontend.Syntax.SyntaxTree (CompUnit (..), TopDecl (..)) +import Solcore.Pipeline.Options (Option (..)) +import Solcore.Util.Keccak (keccak256) + +-- | A content-addressed typecheck-cache key (a keccak digest). +newtype TcCacheKey = TcCacheKey ByteString + deriving (Eq, Ord, Show) + +-- | Does this module define any contracts? Only contract-bearing modules are +-- affected by dispatch generation, so only they fold the -g flag into their +-- key — keeping library (std) keys stable across -g toggles in the UI. +moduleHasContracts :: CompUnit -> Bool +moduleHasContracts unit = any isContract (contracts unit) + where + isContract (TContr _) = True + isContract _ = False + +-- | The compile flags reaching the typecheck phase, serialized for the key. +-- -g (dispatch) is only included for contract-bearing modules. +flagComponent :: Option -> Bool -> ByteString +flagComponent opts hasContracts = + BC.pack + ( "s=" + ++ show (optNoDesugarCalls opts) + ++ (if hasContracts then ";g=" ++ show (optNoGenDispatch opts) else "") + ) + +-- | Compute the Merkle cache key of every module in the graph. +moduleCacheKeys :: Option -> ModuleGraph -> Either String (Map Mod.ModuleId TcCacheKey) +moduleCacheKeys = moduleCacheKeysWith (\_ h -> h) + +-- | 'moduleCacheKeys' with a hook to perturb a module's content hash — used to +-- simulate edits (bump the edited module's content and observe which keys move) +-- without touching disk or re-typechecking. +-- +-- Modules are folded in dependency order ('moduleOrder' lists dependencies +-- before dependents), so each module's reference keys are already known when it +-- is processed. A missing reference key means a cyclic import group, which this +-- PoC does not yet handle (SCC-group keying is deferred); we fail loudly. +moduleCacheKeysWith :: + (Mod.ModuleId -> ByteString -> ByteString) -> + Option -> + ModuleGraph -> + Either String (Map Mod.ModuleId TcCacheKey) +moduleCacheKeysWith perturb opts graph = + foldl step (Right Map.empty) (moduleOrder graph) + where + step (Left err) _ = Left err + step (Right keys) moduleId = + case mapM (lookupKey keys) refs of + Left err -> Left err + Right refKeys -> + let material = + BS.concat + ( contentHash moduleId + : sort [k | TcCacheKey k <- refKeys] + ++ [flagComponent opts (moduleHasContracts (unitOf moduleId))] + ) + in Right (Map.insert moduleId (TcCacheKey (keccak256 material)) keys) + where + refs = Map.findWithDefault [] moduleId (referenceDependencies graph) + + lookupKey keys ref = + maybe + (Left ("cyclic import group at " ++ Mod.moduleIdDisplay ref ++ " (SCC keying not yet implemented)")) + Right + (Map.lookup ref keys) + + unitOf moduleId = loadedCompUnit (modules graph Map.! moduleId) + contentHash moduleId = perturb moduleId (keccak256 (BC.pack (show (unitOf moduleId)))) + +-- | Every module whose key depends on the given module: the module itself plus +-- everything that transitively references it. Exactly the set that must be +-- re-typechecked when the given module is edited. +transitiveDependents :: ModuleGraph -> Mod.ModuleId -> Set Mod.ModuleId +transitiveDependents graph target = go (Set.singleton target) [target] + where + revAdj = + Map.fromListWith + (++) + [ (ref, [moduleId]) + | (moduleId, refs) <- Map.toList (referenceDependencies graph), + ref <- refs + ] + go seen [] = seen + go seen (x : xs) = + let next = filter (`Set.notMember` seen) (Map.findWithDefault [] x revAdj) + in go (foldr Set.insert seen next) (next ++ xs) diff --git a/src/Solcore/Std/Bundle.hs b/src/Solcore/Std/Bundle.hs new file mode 100644 index 000000000..113a95fc8 --- /dev/null +++ b/src/Solcore/Std/Bundle.hs @@ -0,0 +1,24 @@ +{-# LANGUAGE TemplateHaskell #-} + +-- | The standard library, embedded as in-memory source so it can be mounted +-- into the virtual file system used by the in-memory compiler. +-- +-- The import closure of @std@ is @std -> opcodes@ and @dispatch -> std, +-- opcodes@, so these three files form a self-contained set. Simple contracts +-- (see @test/examples/spec/00answer.solc@) use none of this and can be +-- compiled with dispatch generation disabled (@-g@). +module Solcore.Std.Bundle + ( stdSources, + ) +where + +import Solcore.Std.Embed (embedStringFile) + +-- | Standard-library modules as @(file name, source)@ pairs. File names are +-- relative to the std root the loader mounts them under. +stdSources :: [(FilePath, String)] +stdSources = + [ ("std.solc", $(embedStringFile "std/std.solc")), + ("opcodes.solc", $(embedStringFile "std/opcodes.solc")), + ("dispatch.solc", $(embedStringFile "std/dispatch.solc")) + ] diff --git a/src/Solcore/Std/Embed.hs b/src/Solcore/Std/Embed.hs new file mode 100644 index 000000000..ad65bd476 --- /dev/null +++ b/src/Solcore/Std/Embed.hs @@ -0,0 +1,19 @@ +-- | Compile-time embedding of source files as string literals. +-- +-- Used to bake the standard library into the binary so the in-memory / browser +-- compiler has no runtime file-system dependency. +module Solcore.Std.Embed + ( embedStringFile, + ) +where + +import Language.Haskell.TH (Exp, Q) +import Language.Haskell.TH.Syntax (addDependentFile, lift, runIO) + +-- | Splice the contents of a file (read at compile time) as a 'String' +-- literal. The file is registered as a dependency so edits trigger a rebuild. +embedStringFile :: FilePath -> Q Exp +embedStringFile path = do + addDependentFile path + contents <- runIO (readFile path) + lift contents diff --git a/src/Solcore/Util/Keccak.hs b/src/Solcore/Util/Keccak.hs new file mode 100644 index 000000000..a3aaff0a2 --- /dev/null +++ b/src/Solcore/Util/Keccak.hs @@ -0,0 +1,143 @@ +{-# LANGUAGE BangPatterns #-} + +-- | A pure-Haskell Keccak-256 (the Ethereum variant, i.e. original Keccak +-- padding with the 0x01 domain byte, not FIPS-202 SHA3-256's 0x06). +-- +-- This replaces the C-based cryptonite dependency so the compiler can be +-- cross-compiled to JavaScript with the GHC JS backend. It is only used at +-- compile time to evaluate @keccakLit@, so performance is not a concern. +module Solcore.Util.Keccak + ( keccak256, + ) +where + +import Data.Array.Unboxed (UArray, listArray, (!), (//)) +import Data.Array.Unboxed qualified as A +import Data.Bits (complement, rotateL, shiftL, shiftR, xor, (.&.), (.|.)) +import Data.ByteString (ByteString) +import Data.ByteString qualified as BS +import Data.List qualified as L +import Data.Word (Word64) + +-- | Keccak-256 digest (32 bytes) of a byte string. +keccak256 :: ByteString -> ByteString +keccak256 = squeeze . L.foldl' absorbBlock initialState . chunksOf rateBytes . pad rateBytes + +rateBytes :: Int +rateBytes = 136 -- 1088-bit rate for Keccak-256 (17 lanes) + +type State = UArray Int Word64 + +initialState :: State +initialState = listArray (0, 24) (replicate 25 0) + +-- Lane index for the 5x5 state, column-major so that idx x y = x + 5*y. +idx :: Int -> Int -> Int +idx x y = x + 5 * y + +-- Multi-rate padding pad10*1 with Keccak's 0x01 domain byte. +pad :: Int -> ByteString -> ByteString +pad rate msg = msg <> padding + where + q = rate - (BS.length msg `mod` rate) + padding + | q == 1 = BS.singleton 0x81 + | otherwise = BS.singleton 0x01 <> BS.replicate (q - 2) 0x00 <> BS.singleton 0x80 + +chunksOf :: Int -> ByteString -> [ByteString] +chunksOf n bs + | BS.null bs = [] + | otherwise = BS.take n bs : chunksOf n (BS.drop n bs) + +-- Read 8 little-endian bytes at byte offset (8*lane) of a rate block. +laneAt :: ByteString -> Int -> Word64 +laneAt block lane = + L.foldl' (\ !acc k -> acc .|. (fromIntegral (BS.index block (base + k)) `shiftL` (8 * k))) 0 [0 .. 7] + where + base = 8 * lane + +absorbBlock :: State -> ByteString -> State +absorbBlock st block = keccakF (st // [(i, (st ! i) `xor` laneAt block i) | i <- [0 .. 16]]) + +squeeze :: State -> ByteString +squeeze st = + BS.pack + [fromIntegral ((st ! lane) `shiftR` (8 * byte)) | lane <- [0 .. 3], byte <- [0 .. 7]] + +keccakF :: State -> State +keccakF s0 = L.foldl' applyRound s0 roundConstants + where + applyRound a rc = + let cs = listArray (0, 4) [L.foldl' xor 0 [a ! idx x y | y <- [0 .. 4]] | x <- [0 .. 4]] :: UArray Int Word64 + ds = listArray (0, 4) [(cs ! ((x + 4) `mod` 5)) `xor` rotateL (cs ! ((x + 1) `mod` 5)) 1 | x <- [0 .. 4]] :: UArray Int Word64 + theta = A.array (0, 24) [(idx x y, (a ! idx x y) `xor` (ds ! x)) | x <- [0 .. 4], y <- [0 .. 4]] :: UArray Int Word64 + b = + A.array + (0, 24) + [(idx y ((2 * x + 3 * y) `mod` 5), rotateL (theta ! idx x y) (rhoOffsets ! idx x y)) | x <- [0 .. 4], y <- [0 .. 4]] :: + UArray Int Word64 + chi = + A.array + (0, 24) + [(idx x y, (b ! idx x y) `xor` (complement (b ! idx ((x + 1) `mod` 5) y) .&. (b ! idx ((x + 2) `mod` 5) y))) | x <- [0 .. 4], y <- [0 .. 4]] :: + UArray Int Word64 + in chi // [(0, (chi ! 0) `xor` rc)] + +rhoOffsets :: UArray Int Int +rhoOffsets = + listArray + (0, 24) + [ 0, + 1, + 62, + 28, + 27, + 36, + 44, + 6, + 55, + 20, + 3, + 10, + 43, + 25, + 39, + 41, + 45, + 15, + 21, + 8, + 18, + 2, + 61, + 56, + 14 + ] + +roundConstants :: [Word64] +roundConstants = + [ 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008 + ] diff --git a/test/InMemoryApiTests.hs b/test/InMemoryApiTests.hs new file mode 100644 index 000000000..c66782071 --- /dev/null +++ b/test/InMemoryApiTests.hs @@ -0,0 +1,70 @@ +module InMemoryApiTests where + +import Data.List (isInfixOf) +import Solcore.Api (CompileResult (..), compileSolcore, defaultOptions) +import Solcore.Pipeline.Options (Option (..)) +import Test.Tasty +import Test.Tasty.HUnit + +-- A simple contract that uses no std (compiled with dispatch generation off, +-- i.e. the CLI's -g). Mirrors test/examples/spec/00answer.solc. +simpleSource :: String +simpleSource = + unlines + [ "contract Answer {", + " public function main() -> word {", + " return 42;", + " }", + "}" + ] + +noDispatch :: Option +noDispatch = defaultOptions {optNoGenDispatch = True} + +inMemoryApiTests :: TestTree +inMemoryApiTests = + testGroup + "In-memory API" + [ testCase "compiles a simple std-free contract with -g (hull + yul)" $ do + result <- compileSolcore noDispatch simpleSource + compileErrors result @?= [] + assertBool "expected non-empty hull output" (maybe False (not . null) (compileOutput result)) + assertBool "expected non-empty yul output" (maybe False (not . null) (compileYul result)) + assertBool + "yul should be a Yul object" + (maybe False (\y -> "object" `isInfixOf` y) (compileYul result)), + testCase "emits a JSON ABI for the entry module's contract" $ do + result <- compileSolcore noDispatch simpleSource + case compileAbis result of + [(contractName, abiJson)] -> do + contractName @?= "Answer" + assertBool "ABI should describe the main function" ("\"main\"" `isInfixOf` abiJson) + assertBool "ABI should be a JSON array" ("[" `isInfixOf` abiJson) + other -> assertFailure ("expected exactly one contract ABI, got " ++ show (map fst other)), + -- The canonical dispatch example, fed in as source text (as the UI would) + -- and compiled with the standard library resolved from the in-memory + -- bundle. It imports std, std.dispatch and std.opcodes. + testCase "compiles the dispatch/basic.solc example against bundled std" $ do + source <- readFile "test/examples/dispatch/basic.solc" + result <- compileSolcore defaultOptions source + compileErrors result @?= [] + assertBool "expected non-empty hull output" (maybe False (not . null) (compileOutput result)) + assertBool "expected non-empty yul output" (maybe False (not . null) (compileYul result)), + testCase "reports a parse error as a diagnostic" $ do + result <- compileSolcore defaultOptions "this is not solcore" + compileOutput result @?= Nothing + assertBool "expected diagnostics" (not (null (compileErrors result))), + testCase "reports every module as a cache hit on identical recompile" $ do + source <- readFile "test/examples/dispatch/basic.solc" + _ <- compileSolcore defaultOptions source -- warm the session cache + again <- compileSolcore defaultOptions source + let status = compileCacheStatus again + assertBool "expected per-module cache status" (not (null status)) + assertBool "expected std among reported modules" (any (("std" ==) . fst) status) + assertBool + "expected all modules reused on identical recompile" + (all snd status) + -- The ABI is read from the entry module's prepared input; assert it is + -- still produced when that module is served from the session cache. + assertBool "expected an ABI even on a cache-hit recompile" (not (null (compileAbis again))) + ] diff --git a/test/KeccakTests.hs b/test/KeccakTests.hs new file mode 100644 index 000000000..8c8a64863 --- /dev/null +++ b/test/KeccakTests.hs @@ -0,0 +1,29 @@ +module KeccakTests where + +import Data.ByteString qualified as BS +import Data.ByteString.Char8 qualified as C8 +import Numeric (showHex) +import Solcore.Util.Keccak (keccak256) +import Test.Tasty +import Test.Tasty.HUnit + +hexOf :: BS.ByteString -> String +hexOf = concatMap byte . BS.unpack + where + byte b = let h = showHex b "" in if length h == 1 then '0' : h else h + +-- Canonical, independently-published Keccak-256 (Ethereum variant) vectors. +-- These cover the single-block absorb path, which is the only one the compiler +-- exercises (keccakLit hashes short function-signature strings). +keccakTests :: TestTree +keccakTests = + testGroup + "Keccak-256" + [ testCase "empty string" $ + hexOf (keccak256 (C8.pack "")) @?= "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + testCase "abc" $ + hexOf (keccak256 (C8.pack "abc")) @?= "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", + testCase "pangram" $ + hexOf (keccak256 (C8.pack "The quick brown fox jumps over the lazy dog")) + @?= "4d741b6f1eb29cb2a9b9911c82f56fa8d73b04959d3d9d222895df6c0b28aa15" + ] diff --git a/test/Main.hs b/test/Main.hs index c5a06387e..bdd1d0203 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -5,11 +5,14 @@ import ContractAbiTests import DiagnosticCliTests import DiagnosticTests import HullCases +import InMemoryApiTests +import KeccakTests import LocationTests import MatchCompilerTests import ModuleTypeCheckTests import ParserTests import SpecialiseTests +import TcCacheTests import Test.Tasty import YulEvalTests @@ -38,5 +41,8 @@ tests = matchTests, yulEvalTests, hullTests, - specialiseTests + specialiseTests, + inMemoryApiTests, + tcCacheTests, + keccakTests ] diff --git a/test/TcCacheTests.hs b/test/TcCacheTests.hs new file mode 100644 index 000000000..4b015e1b4 --- /dev/null +++ b/test/TcCacheTests.hs @@ -0,0 +1,57 @@ +-- | Round-trip tests for the typecheck-cache persistence layer (Tier 2). +-- +-- The property under test: dumping the std-library subset of a session cache to +-- a blob and reloading it reproduces a cold compile byte-for-byte. This is the +-- path that exercises @binary@ serialization end to end — encode, decode, and +-- the 'fromCachedModule' reconstruction whose dropped fields are loud error +-- thunks. If any of those thunks were actually forced downstream, the warm +-- compile below would crash rather than match. +module TcCacheTests where + +import Control.Monad.Except (runExceptT) +import Data.Map qualified as Map +import Solcore.Api (defaultOptions, dumpStdCacheBlob, indexCheckedByKey, loadStdCacheBlob, renderObjects) +import Solcore.Frontend.Module.Loader (ModuleGraph (..), loadModuleGraphFromSource) +import Solcore.Pipeline.SolcorePipeline (compileDiagnosticsText, compileGraphWithCache) +import Solcore.Pipeline.TypecheckCache (moduleCacheKeys) +import Test.Tasty +import Test.Tasty.HUnit + +tcCacheTests :: TestTree +tcCacheTests = + testGroup + "Typecheck cache (dump/load round-trip)" + [ testCase "std dump reloaded from a blob reproduces cold output byte-for-byte" $ do + source <- readFile "test/examples/dispatch/basic.solc" + graphE <- loadModuleGraphFromSource source + graph <- case graphE of + Left err -> assertFailure ("graph load failed: " ++ err) + Right g -> pure g + keys <- case moduleCacheKeys defaultOptions graph of + Left err -> assertFailure ("cache keys failed: " ++ err) + Right ks -> pure ks + -- Cold compile with an empty seed: every module is typechecked. + coldE <- runExceptT (compileGraphWithCache defaultOptions graph Map.empty) + (coldObjs, coldChecked) <- case coldE of + Left err -> assertFailure ("cold compile failed: " ++ compileDiagnosticsText err) + Right r -> pure r + -- Persist the std subset and read it back through the blob format. + let session = indexCheckedByKey keys coldChecked + blob = dumpStdCacheBlob session + recovered = loadStdCacheBlob defaultOptions blob + assertBool "expected some std modules to be cached" (not (Map.null recovered)) + -- Seed a fresh compile from the reloaded std entries only. + let seed = + Map.fromList + [ (mid, cm) + | mid <- moduleOrder graph, + Just k <- [Map.lookup mid keys], + Just cm <- [Map.lookup k recovered] + ] + assertBool "expected the seed to cover std modules" (not (Map.null seed)) + warmE <- runExceptT (compileGraphWithCache defaultOptions graph seed) + warmObjs <- case warmE of + Left err -> assertFailure ("warm compile failed: " ++ compileDiagnosticsText err) + Right (objs, _) -> pure objs + renderObjects warmObjs @?= renderObjects coldObjs + ] diff --git a/web/Main.hs b/web/Main.hs new file mode 100644 index 000000000..897483a3e --- /dev/null +++ b/web/Main.hs @@ -0,0 +1,113 @@ +{-# LANGUAGE JavaScriptFFI #-} + +-- | GHCJS FFI boundary: exposes the in-memory solcore compiler to JavaScript. +-- +-- On startup it installs, on @globalThis@: +-- +-- * @compileSolcore(source, flags)@ — a synchronous compile returning +-- @{ ok, output, yul, errors }@. +-- * @solcoreDumpStdCache()@ / @solcoreLoadStdCache(blob)@ — the typecheck +-- cache's persistence hooks. The blob is a Latin-1 string (one byte per +-- char) so it stores directly in IndexedDB; @worker.js@ loads it on startup +-- and writes it back after the first successful compile. +module Main where + +import Control.Monad (foldM) +import Data.List (intercalate) +import GHC.JS.Foreign.Callback (Callback, syncCallback', syncCallback1', syncCallback2') +import GHC.JS.Prim (JSVal, fromJSString, toJSString) +import Solcore.Api (CompileResult (..), compileSolcore, defaultOptions, dumpStdCache, loadStdCache) +import Solcore.Pipeline.Options (Option (..)) + +foreign import javascript "((f) => { globalThis.compileSolcore = f; })" + registerCompile :: Callback (JSVal -> JSVal -> IO JSVal) -> IO () + +foreign import javascript "((f) => { globalThis.solcoreDumpStdCache = f; })" + registerDumpStdCache :: Callback (IO JSVal) -> IO () + +foreign import javascript "((f) => { globalThis.solcoreLoadStdCache = f; })" + registerLoadStdCache :: Callback (JSVal -> IO JSVal) -> IO () + +-- | Marshal a Haskell 'Int' to a JS number (identity across the FFI boundary). +foreign import javascript "((n) => n)" + js_int :: Int -> JSVal + +-- | Read a boolean property from a JS object (missing / falsy => False). +foreign import javascript "((obj, key) => (obj && obj[key] ? 1 : 0))" + js_boolField :: JSVal -> JSVal -> IO Int + +-- | Build the JS result object returned to the caller. @abis@ is an object +-- mapping each contract name to its JSON ABI string. +foreign import javascript "((ok, output, yul, errors, cache, abis) => ({ ok: ok !== 0, output: output, yul: yul, errors: errors, cache: cache, abis: abis }))" + js_result :: Int -> JSVal -> JSVal -> JSVal -> JSVal -> JSVal -> IO JSVal + +-- | An empty JS object, and a setter, so we can fold the ABI list into a +-- @{ contractName: abiJson }@ map without a JSON round-trip. +foreign import javascript "(() => ({}))" + js_newObject :: IO JSVal + +foreign import javascript "((obj, key, val) => { obj[key] = val; return obj; })" + js_setProp :: JSVal -> JSVal -> JSVal -> IO JSVal + +boolField :: JSVal -> String -> IO Bool +boolField obj key = (/= 0) <$> js_boolField obj (toJSString key) + +-- | Map the flags object (UI checkboxes) onto compiler options. +optionsFromFlags :: JSVal -> IO Option +optionsFromFlags flags = do + noGenDispatch <- boolField flags "noGenDispatch" + noSpec <- boolField flags "noSpec" + noMatchCompiler <- boolField flags "noMatchCompiler" + noIfDesugar <- boolField flags "noIfDesugar" + noDesugarCalls <- boolField flags "noDesugarCalls" + pure + defaultOptions + { optNoGenDispatch = noGenDispatch, + optNoSpec = noSpec, + optNoMatchCompiler = noMatchCompiler, + optNoIfDesugar = noIfDesugar, + optNoDesugarCalls = noDesugarCalls + } + +-- | Render the per-module cache status for the UI, e.g. +-- @"std: cache hit; std.dispatch: cache miss; Main: cache miss"@. +renderCacheStatus :: [(String, Bool)] -> String +renderCacheStatus status = + intercalate "; " [name ++ ": " ++ (if hit then "cache hit" else "cache miss") | (name, hit) <- status] + +-- | Marshal the ABI list into a JS object mapping contract name to ABI JSON. +abisToJs :: [(String, String)] -> IO JSVal +abisToJs abis = do + obj <- js_newObject + foldM (\o (n, j) -> js_setProp o (toJSString n) (toJSString j)) obj abis + +compile :: JSVal -> JSVal -> IO JSVal +compile sourceVal flagsVal = do + opts <- optionsFromFlags flagsVal + result <- compileSolcore opts (fromJSString sourceVal) + let cache = toJSString (renderCacheStatus (compileCacheStatus result)) + abis <- abisToJs (compileAbis result) + -- Any diagnostic is a failure, even when a hull was produced: a Yul + -- translation error leaves 'compileOutput' set but 'compileErrors' non-empty, + -- and must still surface in the result pane rather than read as success. + let ok = if null (compileErrors result) then 1 else 0 + output = maybe "" id (compileOutput result) + yul = maybe "" id (compileYul result) + errors = intercalate "\n\n" (compileErrors result) + js_result ok (toJSString output) (toJSString yul) (toJSString errors) cache abis + +-- | Dump the persisted (std) typecheck cache as a Latin-1 string. +dumpCache :: IO JSVal +dumpCache = toJSString <$> dumpStdCache + +-- | Merge a persisted cache blob into the session, returning the count loaded. +loadCache :: JSVal -> IO JSVal +loadCache blob = js_int <$> loadStdCache (fromJSString blob) + +main :: IO () +main = do + -- Register the cache hooks before the compile entry point, so that once + -- @compileSolcore@ is visible (what worker.js polls on) all three exist. + syncCallback' dumpCache >>= registerDumpStdCache + syncCallback1' loadCache >>= registerLoadStdCache + syncCallback2' compile >>= registerCompile diff --git a/web/build.sh b/web/build.sh new file mode 100755 index 000000000..30c81947e --- /dev/null +++ b/web/build.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# Build the in-browser solcore compiler and assemble a servable site in web/site. +# Needs the JS-backend compiler (javascript-unknown-ghcjs-ghc), which the default +# dev shell deliberately omits (building it is a from-source GHC cross-compile). +# Get it from this repo's ghcjs-flake.nix: cp ghcjs-flake.nix flake.nix && nix develop +# (the sibling flake, nix develop ../ghcjs, also provides it). +# +# ./web/build.sh dev build (-O0, fast, unminified) +# ./web/build.sh --release release (-O1, esbuild-minified, + precompressed .gz) +set -euo pipefail + +cd "$(dirname "$0")/.." + +# The JS-backend compiler is not in the default dev shell. Fail early with a +# usable hint rather than a cryptic Cabal error. +hint="enter a shell with it first, e.g. cp ghcjs-flake.nix flake.nix && nix develop (or nix develop ../ghcjs)" +if ! command -v javascript-unknown-ghcjs-ghc >/dev/null 2>&1; then + echo "error: javascript-unknown-ghcjs-ghc not on PATH — $hint" >&2 + exit 1 +fi + +release=0 +[ "${1:-}" = "--release" ] && release=1 + +if [ "$release" = 1 ]; then + project=cabal-ghcjs-o1.project + builddir=dist-ghcjs-o1 +else + project=cabal-ghcjs.project + builddir=dist-ghcjs +fi + +# Wipe the build dir when package/project metadata changed since the last +# successful build. Cabal's incremental multi-package plan doesn't survive a +# change in module lists and fails with confusing package-id conflicts, so a +# clean rebuild is required in that case (and only that case). +stamp="$builddir/.build-stamp" +metadata=(sol-core.cabal web/solcore-web.cabal "$project") +clean=0 +if [ ! -f "$stamp" ]; then + clean=1 +else + for f in "${metadata[@]}"; do + if [ "$f" -nt "$stamp" ]; then + echo "metadata ($f) changed since last build — cleaning $builddir" + clean=1 + break + fi + done +fi + +# Guard against a JS build plan contaminated with a foreign GHC's package-ids. +# A `cabal update` (or any resolve run without the JS toolchain in scope) can +# rewrite $builddir/cache/plan.json with the *native* ghc's ids; the JS compiler +# then fails later with a cryptic `cannot satisfy -package-id base-...`. Compare +# the base package-id baked into the cached plan against the one this JS +# toolchain actually provides; on a mismatch the plan is stale and the builddir +# must be regenerated (deps stay in the global store, so only the local packages +# recompile). +plan="$builddir/cache/plan.json" +if [ "$clean" = 0 ] && [ -f "$plan" ]; then + actual_base=$(javascript-unknown-ghcjs-ghc-pkg field base id 2>/dev/null | awk '{print $2}') + if [ -n "$actual_base" ] && ! grep -q "\"$actual_base\"" "$plan"; then + echo "JS build plan references a foreign base package-id (expected $actual_base) — cleaning $builddir" + clean=1 + fi +fi + +if [ "$clean" = 1 ] && [ -d "$builddir" ]; then + rm -rf "$builddir" +fi + +cabal build exe:solcore-web --project-file="$project" --builddir="$builddir" +# Record a successful configure/build so the next run only cleans on real changes. +touch "$stamp" +jsexe="$(cabal list-bin exe:solcore-web --project-file="$project" --builddir="$builddir").jsexe" + +rm -rf web/site +mkdir -p web/site +cp web/index.html web/site/index.html # the React IDE (entry point) +cp web/simple.html web/site/simple.html # the minimal two-textarea page +cp web/worker.js web/site/worker.js +cp -r web/vendor web/site/vendor # local React UMD (no CDN dependency) + +# Precompile the IDE's JSX to plain JS with esbuild (no in-browser Babel). +# --bundle + the .solc text loader inline the real std sources (single source of +# truth) into ide.js; React/ReactDOM stay UMD globals (referenced, not imported). +npx --yes esbuild@0.24.0 web/ide.jsx --bundle --jsx=transform --loader:.solc=text \ + --minify --outfile=web/site/ide.js + +if [ "$release" = 1 ]; then + # Minify with esbuild (fetched on demand via npx). The win over raw GHCJS + # output is small — the codegen is already compact — so fall back gracefully. + if npx --yes esbuild@0.24.0 "$jsexe/all.js" --minify --outfile=web/site/all.js 2>/dev/null; then + echo "minified all.js with esbuild" + else + echo "warning: esbuild unavailable, using unminified all.js" >&2 + cp "$jsexe/all.js" web/site/all.js + fi + # Precompress for hosts serving with `precompressed gzip` (e.g. Caddy). + gzip -9 -kf web/site/all.js +else + cp "$jsexe/all.js" web/site/all.js +fi + +# Precompiled std typecheck cache, so the browser's first-ever compile (empty +# IndexedDB) starts warm. Generated with the native toolchain (deterministic, +# unlike driving the synchronous JS compile under Node); the content-hash keys +# and serialized AST are toolchain-independent, so the blob is byte-identical to +# one the JS build would dump. Uses the default (native) cabal project. +cabal run -v0 exe:gen-std-cache -- web/site/std-cache.bin +[ "$release" = 1 ] && gzip -9 -kf web/site/std-cache.bin + +# Node-runnable CLI for benchmarking/testing the compiler off the browser: the +# driver waits for the globalThis.compileSolcore that all.js installs, so it is +# simply prepended to all.js (see web/node-driver.cjs for why not imported). +cat web/node-driver.cjs web/site/all.js > web/site/solcore-node.cjs + +mode=$([ "$release" = 1 ] && echo "release (-O1, minified)" || echo "dev (-O0)") +echo "Built: $mode" +# .gz files only exist for release builds; tolerate their absence under pipefail. +ls -la web/site/all.js web/site/all.js.gz web/site/std-cache.bin web/site/std-cache.bin.gz 2>/dev/null | awk '{print " " $5 " " $NF}' || true +echo "Serve with: (cd web/site && python3 -m http.server 8000)" +echo " React IDE (main): http://localhost:8000/index.html" +echo " simple page: http://localhost:8000/simple.html" +echo "Bench with: node web/site/solcore-node.cjs [iterations]" diff --git a/web/ide.jsx b/web/ide.jsx new file mode 100644 index 000000000..d9308ae46 --- /dev/null +++ b/web/ide.jsx @@ -0,0 +1,252 @@ +// solcore IDE — React tabs/file-tree frontend over the GHCJS solcore backend. +// Compiled to plain JS by esbuild at build time (see web/build.sh); React and +// ReactDOM are provided as globals by the vendored UMD scripts. Compilation +// runs in a Web Worker (web/worker.js) so the main thread stays free to render +// a live timer. +// +// Workspace layout mirrors Remix: `contracts/` holds the editable sources you +// compile, `artifacts/` is filled with the hull + yul the compiler emits, and +// `std/` exposes the (read-only) library sources baked into the compiler. +const { useState, useReducer, useCallback, useEffect, useRef } = React; + +// Real std library sources, inlined at build time by esbuild's text loader +// (single source of truth: these are the same files the compiler embeds). +import stdSrc from "../std/std.solc"; +import opcodesSrc from "../std/opcodes.solc"; +import dispatchSrc from "../std/dispatch.solc"; + +// ---- editable contracts (seed the `contracts/` directory) ---- +const CONTRACT_SEED = [ + { name: "Answer.solc", path: "contracts/Answer.solc", + content: "contract Answer {\n public function main() -> word {\n return 42;\n }\n}\n" }, + { name: "Counter.solc", path: "contracts/Counter.solc", + content: "import std.{*};\nimport std.dispatch.{*};\n\ncontract Counter {\n counter : uint256;\n\n constructor() { counter = uint256(42); }\n\n public function get() -> uint256 {\n return counter;\n }\n}\n" }, + { name: "basic.solc", path: "contracts/basic.solc", + content: "import std.{*};\nimport std.dispatch.{*};\nimport std.opcodes.{address as address_};\n\nfunction self() -> address {\n return address(address_());\n}\n\ncontract C {\n constructor() {}\n public function nothing() -> () {}\n\n // Re-enters this very contract via raw_call(address(this), ...). The payload\n // is the 4-byte selector of an existing entry point (something(), 0xa7a0d537),\n // built by left-aligning it in a bytes32 and truncating to 4 bytes. The inner\n // call succeeds, so raw_call reports ok == true and returns its returndata\n // (the abi-encoded uint256(1)).\n public function callSelf() -> (bool, memory(bytes)) {\n let sel: bytes32 = bytes32(0xa7a0d53700000000000000000000000000000000000000000000000000000000);\n let payload = truncate(to_bytes(sel), 4);\n match raw_call(self(), uint256(0), payload) {\n | (ok, ret) => return (ok, ret);\n }\n }\n\n // Same shape, but the selector (0xdeadc0de) matches no entry point, so dispatch\n // reverts (there is no fallback). raw_call swallows the inner revert and reports\n // ok == false; this outer call itself still succeeds and returns the revert\n // returndata (the 4-byte NoFallback error selector).\n public function callSelfInvalid() -> (bool, memory(bytes)) {\n let sel: bytes32 = bytes32(0xdeadc0de00000000000000000000000000000000000000000000000000000000);\n let payload = truncate(to_bytes(sel), 4);\n match raw_call(self(), uint256(0), payload) {\n | (ok, ret) => return (ok, ret);\n }\n }\n\n public function something() -> (uint256) {\n return uint256(1);\n }\n\n public function add2(x : uint256, y : uint256) -> uint256 {\n return Add.add(x,y);\n }\n\n public function add3(x : uint256, y : uint256, z : uint256) -> uint256 {\n return Add.add(z, Add.add(x,y));\n }\n\n public function addmod3(x : uint256, y : uint256, k : uint256) -> uint256 {\n return addmod(x, y, k);\n }\n\n public function mulmod3(x : uint256, y : uint256, k : uint256) -> uint256 {\n return mulmod(x, y, k);\n }\n\n // Bitwise / modulo via the syntactic sugar only (no explicit class calls):\n // `^` -> BitXor.bxor, `|` -> BitOr.bor, `&` -> BitAnd.band, `%` -> Mod.mod.\n public function bxor2(x : uint256, y : uint256) -> uint256 {\n return x ^ y;\n }\n\n public function bor2(x : uint256, y : uint256) -> uint256 {\n return x | y;\n }\n\n public function band2(x : uint256, y : uint256) -> uint256 {\n return x & y;\n }\n\n public function mod2(x : uint256, y : uint256) -> uint256 {\n return x % y;\n }\n\n public function id_bytes(b: memory(bytes)) -> memory(bytes) {\n return b;\n }\n\n public function id_string(b: memory(string)) -> memory(string) {\n return b;\n }\n\n public function id_bytes32(b: bytes32) -> bytes32 {\n return b;\n }\n\n public function id_address(a: address) -> address {\n return a;\n }\n\n public function id_pair() -> (uint256, uint256) {\n return (uint256(7), uint256(11));\n }\n\n function hidden() -> (uint256) {\n return uint256(42);\n }\n}\n" }, + { name: "Scratch.solc", path: "contracts/Scratch.solc", + content: "contract Scratch {\n public function main() -> word {\n return 7;\n }\n}\n" }, +]; + +// Read-only library sources (the `std/` directory). +const STD_SEED = [ + { name: "std.solc", path: "std/std.solc", content: stdSrc }, + { name: "opcodes.solc", path: "std/opcodes.solc", content: opcodesSrc }, + { name: "dispatch.solc", path: "std/dispatch.solc", content: dispatchSrc }, +]; + +const READONLY_CONTENT = Object.fromEntries(STD_SEED.map(f => [f.path, f.content])); +const initialFiles = Object.fromEntries(CONTRACT_SEED.map(f => [f.path, f.content])); + +const basename = (p) => p.split("/").pop(); +const isContract = (p) => !!p && p.startsWith("contracts/"); +const artifactBase = (p) => basename(p).replace(/\.solc$/, ""); + +// Build the workspace tree from the static dirs plus the live artifacts map. +function buildTree(artifacts) { + const fileNode = ({ name, path }) => ({ name, path, isDirectory: false }); + const dir = (name, children) => ({ name, path: name, isDirectory: true, children }); + const artifactNodes = Object.keys(artifacts).sort() + .map(p => ({ name: basename(p), path: p, isDirectory: false })); + return dir("workspace", [ + dir("contracts", CONTRACT_SEED.map(fileNode)), + dir("std", STD_SEED.map(fileNode)), + dir("artifacts", artifactNodes), + ]); +} + +// ---- tab controller (mirrors apps/remix-ide tab-proxy: loadedTabs + open/close/select) ---- +const initialTabs = { open: [], active: null }; +function tabsReducer(state, action) { + switch (action.type) { + case "OPEN": { + const open = state.open.includes(action.path) ? state.open : [...state.open, action.path]; + return { open, active: action.path }; + } + case "SELECT": + return { ...state, active: action.path }; + case "CLOSE": { + const idx = state.open.indexOf(action.path); + if (idx === -1) return state; + const open = state.open.filter(p => p !== action.path); + let active = state.active; + if (state.active === action.path) { // pick a neighbour, like tab-proxy + active = open[idx - 1] || open[idx] || null; + } + return { open, active }; + } + default: return state; + } +} + +// ---- tree view (mirrors remix-ui/tree-view: recursive ul/li with caret) ---- +function TreeNode({ node, depth, activePath, onOpen }) { + const [expanded, setExpanded] = useState(true); + const pad = { paddingLeft: depth * 12 }; + if (node.isDirectory) { + return ( +
  • +
    setExpanded(!expanded)}> + {expanded ? "▾" : "▸"}{node.name} +
    + {expanded && ( +
      {node.children.map(c => + )} +
    + )} +
  • + ); + } + return ( +
  • +
    onOpen(node.path)}> + 📄{node.name} +
    +
  • + ); +} + +// ---- tabs bar (mirrors remix-ui/tabs: title + middle-click / × to close) ---- +function TabsBar({ open, active, onSelect, onClose }) { + return ( +
    + {open.map(path => ( +
    onSelect(path)} + onMouseDown={(e) => { if (e.button === 1) { e.preventDefault(); onClose(path); } }}> + {basename(path)} + { e.stopPropagation(); onClose(path); }}>× +
    + ))} +
    + ); +} + +const FLAGS = [ + ["noGenDispatch", "-g no dispatch"], +]; + +function App() { + const [files, setFiles] = useState(initialFiles); // editable contracts/* + const [artifacts, setArtifacts] = useState({}); // artifacts/* (hull + yul), read-only + const [tabs, dispatch] = useReducer(tabsReducer, initialTabs); + const [flags, setFlags] = useState({ noGenDispatch: true }); + const [result, setResult] = useState({ ok: true, message: "", cache: "" }); + const [ready, setReady] = useState(false); + const [compiling, setCompiling] = useState(false); + const [elapsed, setElapsed] = useState(null); // ms; null before the first compile + + const workerRef = useRef(null); + const timerRef = useRef(null); + const startRef = useRef(0); + const compilingPathRef = useRef(null); // which contract this compile is for + + // Content of any path, whether editable (files) or read-only (std / artifacts). + const contentOf = (path) => + path in artifacts ? artifacts[path] : path in READONLY_CONTENT ? READONLY_CONTENT[path] : files[path]; + const editable = (path) => isContract(path); + + // Spin up the compiler worker once; it posts "ready" when the bundle loads. + useEffect(() => { + const worker = new Worker("worker.js"); + workerRef.current = worker; + worker.onmessage = (e) => { + const msg = e.data; + if (msg.type === "ready") { setReady(true); return; } + if (msg.type === "result") { + clearInterval(timerRef.current); + setElapsed(performance.now() - startRef.current); // freeze final time + setCompiling(false); + const path = compilingPathRef.current; + if (msg.ok) { + const base = artifactBase(path); + const hullPath = `artifacts/${base}.hull`; + const yulPath = `artifacts/${base}.yul`; + // One .abi per contract in the source (named by contract, like the CLI). + const abiEntries = Object.entries(msg.abis || {}) + .map(([name, json]) => [`artifacts/${name}.abi`, json]); + const added = { [hullPath]: msg.output, [yulPath]: msg.yul, ...Object.fromEntries(abiEntries) }; + setArtifacts(a => ({ ...a, ...added })); + const lines = Object.keys(added).map(p => ` → ${p}`).join("\n"); + setResult({ ok: true, message: `✓ compiled ${path}\n${lines}`, cache: msg.cache || "" }); + } else { + setResult({ ok: false, message: msg.errors, cache: msg.cache || "" }); + } + } + }; + return () => worker.terminate(); + }, []); + + const openFile = useCallback((path) => dispatch({ type: "OPEN", path }), []); + const editActive = (content) => { + if (editable(tabs.active)) setFiles(f => ({ ...f, [tabs.active]: content })); + }; + + const compile = () => { + if (!isContract(tabs.active) || !ready || compiling) return; + setCompiling(true); + compilingPathRef.current = tabs.active; + startRef.current = performance.now(); + setElapsed(0); + // Tick the timer while the worker compiles. + timerRef.current = setInterval(() => setElapsed(performance.now() - startRef.current), 47); + workerRef.current.postMessage({ type: "compile", source: files[tabs.active], flags }); + }; + + const tree = buildTree(artifacts); + const canCompile = ready && !compiling && isContract(tabs.active); + const status = + !ready ? "loading compiler…" + : compiling ? "compiling… " + Math.round(elapsed) + " ms" + : elapsed != null ? (result.ok ? "compiled in " : "failed after ") + Math.round(elapsed) + " ms" + : "compiler ready"; + + return ( +
    +
    + +
    + {FLAGS.map(([key, label]) => ( + + ))} +
    + {status} + {!compiling && result.cache + ? {result.cache} + : null} +
    + +
    +
      + +
    +
    + +
    + dispatch({ type: "SELECT", path: p })} + onClose={(p) => dispatch({ type: "CLOSE", path: p })} /> + {tabs.active ? ( + +
    +
    + + +
    +
    + + +
    +
    + + + + + diff --git a/web/solcore-web.cabal b/web/solcore-web.cabal new file mode 100644 index 000000000..ee7833a75 --- /dev/null +++ b/web/solcore-web.cabal @@ -0,0 +1,12 @@ +cabal-version: 3.0 +name: solcore-web +version: 0.0.0.0 +synopsis: GHCJS FFI boundary exposing the solcore compiler to the browser. +build-type: Simple + +executable solcore-web + main-is: Main.hs + hs-source-dirs: . + default-language: Haskell2010 + default-extensions: JavaScriptFFI + build-depends: base, sol-core diff --git a/web/vendor/react-dom.production.min.js b/web/vendor/react-dom.production.min.js new file mode 100644 index 000000000..fb4e099c0 --- /dev/null +++ b/web/vendor/react-dom.production.min.js @@ -0,0 +1,267 @@ +/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +(function(){/* + Modernizr 3.0.0pre (Custom Build) | MIT +*/ +'use strict';(function(Q,zb){"object"===typeof exports&&"undefined"!==typeof module?zb(exports,require("react")):"function"===typeof define&&define.amd?define(["exports","react"],zb):(Q=Q||self,zb(Q.ReactDOM={},Q.React))})(this,function(Q,zb){function m(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;cb}return!1}function Y(a,b,c,d,e,f,g){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f;this.removeEmptyString=g}function $d(a,b,c,d){var e=R.hasOwnProperty(b)?R[b]:null;if(null!==e?0!==e.type:d||!(2h||e[g]!==f[h]){var k="\n"+e[g].replace(" at new "," at ");a.displayName&&k.includes("")&&(k=k.replace("",a.displayName));return k}while(1<=g&&0<=h)}break}}}finally{ce=!1,Error.prepareStackTrace=c}return(a=a?a.displayName||a.name:"")?bc(a): +""}function fj(a){switch(a.tag){case 5:return bc(a.type);case 16:return bc("Lazy");case 13:return bc("Suspense");case 19:return bc("SuspenseList");case 0:case 2:case 15:return a=be(a.type,!1),a;case 11:return a=be(a.type.render,!1),a;case 1:return a=be(a.type,!0),a;default:return""}}function de(a){if(null==a)return null;if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case Bb:return"Fragment";case Cb:return"Portal";case ee:return"Profiler";case fe:return"StrictMode"; +case ge:return"Suspense";case he:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case gg:return(a.displayName||"Context")+".Consumer";case hg:return(a._context.displayName||"Context")+".Provider";case ie:var b=a.render;a=a.displayName;a||(a=b.displayName||b.name||"",a=""!==a?"ForwardRef("+a+")":"ForwardRef");return a;case je:return b=a.displayName||null,null!==b?b:de(a.type)||"Memo";case Ta:b=a._payload;a=a._init;try{return de(a(b))}catch(c){}}return null}function gj(a){var b=a.type; +switch(a.tag){case 24:return"Cache";case 9:return(b.displayName||"Context")+".Consumer";case 10:return(b._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return a=b.render,a=a.displayName||a.name||"",b.displayName||(""!==a?"ForwardRef("+a+")":"ForwardRef");case 7:return"Fragment";case 5:return b;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return de(b);case 8:return b===fe?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler"; +case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if("function"===typeof b)return b.displayName||b.name||null;if("string"===typeof b)return b}return null}function Ua(a){switch(typeof a){case "boolean":case "number":case "string":case "undefined":return a;case "object":return a;default:return""}}function ig(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"=== +b)}function hj(a){var b=ig(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker= +null;delete a[b]}}}}function Pc(a){a._valueTracker||(a._valueTracker=hj(a))}function jg(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=ig(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Qc(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}}function ke(a,b){var c=b.checked;return E({},b,{defaultChecked:void 0,defaultValue:void 0, +value:void 0,checked:null!=c?c:a._wrapperState.initialChecked})}function kg(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=Ua(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function lg(a,b){b=b.checked;null!=b&&$d(a,"checked",b,!1)}function le(a,b){lg(a,b);var c=Ua(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!= +c)a.value=""+c}else a.value!==""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?me(a,b.type,c):b.hasOwnProperty("defaultValue")&&me(a,b.type,Ua(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function mg(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue; +c||b===a.value||(a.value=b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function me(a,b,c){if("number"!==b||Qc(a.ownerDocument)!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function Db(a,b,c,d){a=a.options;if(b){b={};for(var e=0;e>>=0;return 0===a?32:31-(qj(a)/rj|0)|0}function hc(a){switch(a&-a){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a& +4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return a&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return a}}function Vc(a,b){var c=a.pendingLanes;if(0===c)return 0;var d=0,e=a.suspendedLanes,f=a.pingedLanes,g=c&268435455;if(0!==g){var h=g&~e;0!==h?d=hc(h):(f&=g,0!==f&&(d=hc(f)))}else g=c&~e,0!==g?d=hc(g):0!==f&&(d=hc(f));if(0===d)return 0;if(0!==b&&b!==d&&0===(b&e)&& +(e=d&-d,f=b&-b,e>=f||16===e&&0!==(f&4194240)))return b;0!==(d&4)&&(d|=c&16);b=a.entangledLanes;if(0!==b)for(a=a.entanglements,b&=d;0c;c++)b.push(a); +return b}function ic(a,b,c){a.pendingLanes|=b;536870912!==b&&(a.suspendedLanes=0,a.pingedLanes=0);a=a.eventTimes;b=31-ta(b);a[b]=c}function uj(a,b){var c=a.pendingLanes&~b;a.pendingLanes=b;a.suspendedLanes=0;a.pingedLanes=0;a.expiredLanes&=b;a.mutableReadLanes&=b;a.entangledLanes&=b;b=a.entanglements;var d=a.eventTimes;for(a=a.expirationTimes;0=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=$g(c)}}function bh(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?bh(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function ch(){for(var a=window,b=Qc();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break; +b=Qc(a.document)}return b}function Ie(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function Tj(a){var b=ch(),c=a.focusedElem,d=a.selectionRange;if(b!==c&&c&&c.ownerDocument&&bh(c.ownerDocument.documentElement,c)){if(null!==d&&Ie(c))if(b=d.start,a=d.end,void 0===a&&(a=b),"selectionStart"in c)c.selectionStart=b,c.selectionEnd=Math.min(a,c.value.length); +else if(a=(b=c.ownerDocument||document)&&b.defaultView||window,a.getSelection){a=a.getSelection();var e=c.textContent.length,f=Math.min(d.start,e);d=void 0===d.end?f:Math.min(d.end,e);!a.extend&&f>d&&(e=d,d=f,f=e);e=ah(c,f);var g=ah(c,d);e&&g&&(1!==a.rangeCount||a.anchorNode!==e.node||a.anchorOffset!==e.offset||a.focusNode!==g.node||a.focusOffset!==g.offset)&&(b=b.createRange(),b.setStart(e.node,e.offset),a.removeAllRanges(),f>d?(a.addRange(b),a.extend(g.node,g.offset)):(b.setEnd(g.node,g.offset), +a.addRange(b)))}b=[];for(a=c;a=a.parentNode;)1===a.nodeType&&b.push({element:a,left:a.scrollLeft,top:a.scrollTop});"function"===typeof c.focus&&c.focus();for(c=0;cMb||(a.current=Se[Mb],Se[Mb]=null,Mb--)} +function y(a,b,c){Mb++;Se[Mb]=a.current;a.current=b}function Nb(a,b){var c=a.type.contextTypes;if(!c)return cb;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function ea(a){a=a.childContextTypes;return null!==a&&void 0!==a}function th(a,b,c){if(J.current!==cb)throw Error(m(168)); +y(J,b);y(S,c)}function uh(a,b,c){var d=a.stateNode;b=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in b))throw Error(m(108,gj(a)||"Unknown",e));return E({},c,d)}function ld(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||cb;pb=J.current;y(J,a);y(S,S.current);return!0}function vh(a,b,c){var d=a.stateNode;if(!d)throw Error(m(169));c?(a=uh(a,b,pb),d.__reactInternalMemoizedMergedChildContext=a,v(S),v(J),y(J,a)):v(S); +y(S,c)}function wh(a){null===La?La=[a]:La.push(a)}function jk(a){md=!0;wh(a)}function db(){if(!Te&&null!==La){Te=!0;var a=0,b=z;try{var c=La;for(z=1;a>=g;e-=g;Ma=1<<32-ta(b)+e|c<t?(q=l,l=null):q=l.sibling;var A=r(e,l,h[t],k);if(null===A){null===l&&(l=q);break}a&&l&&null===A.alternate&&b(e,l);g=f(A,g,t);null===m?n=A:m.sibling=A;m=A;l=q}if(t===h.length)return c(e,l),D&&qb(e,t),n;if(null===l){for(;t< +h.length;t++)l=u(e,h[t],k),null!==l&&(g=f(l,g,t),null===m?n=l:m.sibling=l,m=l);D&&qb(e,t);return n}for(l=d(e,l);tt?(A=q,q=null):A=q.sibling;var x=r(e,q,w.value,k);if(null===x){null===q&&(q=A);break}a&&q&&null===x.alternate&&b(e,q);g=f(x,g,t);null===l?n=x:l.sibling=x;l=x;q=A}if(w.done)return c(e,q),D&&qb(e,t),n;if(null===q){for(;!w.done;t++,w=h.next())w=u(e,w.value,k),null!==w&&(g=f(w,g,t),null===l?n=w:l.sibling=w,l=w);D&&qb(e,t);return n}for(q=d(e,q);!w.done;t++,w=h.next())w=p(q,e,t,w.value,k),null!==w&&(a&&null!==w.alternate&&q.delete(null===w.key?t:w.key),g=f(w,g,t),null===l?n=w:l.sibling= +w,l=w);a&&q.forEach(function(a){return b(e,a)});D&&qb(e,t);return n}function v(a,d,f,h){"object"===typeof f&&null!==f&&f.type===Bb&&null===f.key&&(f=f.props.children);if("object"===typeof f&&null!==f){switch(f.$$typeof){case sd:a:{for(var k=f.key,n=d;null!==n;){if(n.key===k){k=f.type;if(k===Bb){if(7===n.tag){c(a,n.sibling);d=e(n,f.props.children);d.return=a;a=d;break a}}else if(n.elementType===k||"object"===typeof k&&null!==k&&k.$$typeof===Ta&&Ch(k)===n.type){c(a,n.sibling);d=e(n,f.props);d.ref=vc(a, +n,f);d.return=a;a=d;break a}c(a,n);break}else b(a,n);n=n.sibling}f.type===Bb?(d=sb(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=rd(f.type,f.key,f.props,null,a.mode,h),h.ref=vc(a,d,f),h.return=a,a=h)}return g(a);case Cb:a:{for(n=f.key;null!==d;){if(d.key===n)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=$e(f,a.mode,h);d.return=a; +a=d}return g(a);case Ta:return n=f._init,v(a,d,n(f._payload),h)}if(cc(f))return x(a,d,f,h);if(ac(f))return I(a,d,f,h);qd(a,f)}return"string"===typeof f&&""!==f||"number"===typeof f?(f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=Ze(f,a.mode,h),d.return=a,a=d),g(a)):c(a,d)}return v}function af(){bf=Rb=td=null}function cf(a,b){b=ud.current;v(ud);a._currentValue=b}function df(a,b,c){for(;null!==a;){var d=a.alternate;(a.childLanes&b)!==b?(a.childLanes|=b,null!==d&&(d.childLanes|= +b)):null!==d&&(d.childLanes&b)!==b&&(d.childLanes|=b);if(a===c)break;a=a.return}}function Sb(a,b){td=a;bf=Rb=null;a=a.dependencies;null!==a&&null!==a.firstContext&&(0!==(a.lanes&b)&&(ha=!0),a.firstContext=null)}function qa(a){var b=a._currentValue;if(bf!==a)if(a={context:a,memoizedValue:b,next:null},null===Rb){if(null===td)throw Error(m(308));Rb=a;td.dependencies={lanes:0,firstContext:a}}else Rb=Rb.next=a;return b}function ef(a){null===tb?tb=[a]:tb.push(a)}function Eh(a,b,c,d){var e=b.interleaved; +null===e?(c.next=c,ef(b)):(c.next=e.next,e.next=c);b.interleaved=c;return Oa(a,d)}function Oa(a,b){a.lanes|=b;var c=a.alternate;null!==c&&(c.lanes|=b);c=a;for(a=a.return;null!==a;)a.childLanes|=b,c=a.alternate,null!==c&&(c.childLanes|=b),c=a,a=a.return;return 3===c.tag?c.stateNode:null}function ff(a){a.updateQueue={baseState:a.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Fh(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue= +{baseState:a.baseState,firstBaseUpdate:a.firstBaseUpdate,lastBaseUpdate:a.lastBaseUpdate,shared:a.shared,effects:a.effects})}function Pa(a,b){return{eventTime:a,lane:b,tag:0,payload:null,callback:null,next:null}}function fb(a,b,c){var d=a.updateQueue;if(null===d)return null;d=d.shared;if(0!==(p&2)){var e=d.pending;null===e?b.next=b:(b.next=e.next,e.next=b);d.pending=b;return kk(a,c)}e=d.interleaved;null===e?(b.next=b,ef(d)):(b.next=e.next,e.next=b);d.interleaved=b;return Oa(a,c)}function vd(a,b,c){b= +b.updateQueue;if(null!==b&&(b=b.shared,0!==(c&4194240))){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function Gh(a,b){var c=a.updateQueue,d=a.alternate;if(null!==d&&(d=d.updateQueue,c===d)){var e=null,f=null;c=c.firstBaseUpdate;if(null!==c){do{var g={eventTime:c.eventTime,lane:c.lane,tag:c.tag,payload:c.payload,callback:c.callback,next:null};null===f?e=f=g:f=f.next=g;c=c.next}while(null!==c);null===f?e=f=b:f=f.next=b}else e=f=b;c={baseState:d.baseState,firstBaseUpdate:e,lastBaseUpdate:f, +shared:d.shared,effects:d.effects};a.updateQueue=c;return}a=c.lastBaseUpdate;null===a?c.firstBaseUpdate=b:a.next=b;c.lastBaseUpdate=b}function wd(a,b,c,d){var e=a.updateQueue;gb=!1;var f=e.firstBaseUpdate,g=e.lastBaseUpdate,h=e.shared.pending;if(null!==h){e.shared.pending=null;var k=h,n=k.next;k.next=null;null===g?f=n:g.next=n;g=k;var l=a.alternate;null!==l&&(l=l.updateQueue,h=l.lastBaseUpdate,h!==g&&(null===h?l.firstBaseUpdate=n:h.next=n,l.lastBaseUpdate=k))}if(null!==f){var m=e.baseState;g=0;l= +n=k=null;h=f;do{var r=h.lane,p=h.eventTime;if((d&r)===r){null!==l&&(l=l.next={eventTime:p,lane:0,tag:h.tag,payload:h.payload,callback:h.callback,next:null});a:{var x=a,v=h;r=b;p=c;switch(v.tag){case 1:x=v.payload;if("function"===typeof x){m=x.call(p,m,r);break a}m=x;break a;case 3:x.flags=x.flags&-65537|128;case 0:x=v.payload;r="function"===typeof x?x.call(p,m,r):x;if(null===r||void 0===r)break a;m=E({},m,r);break a;case 2:gb=!0}}null!==h.callback&&0!==h.lane&&(a.flags|=64,r=e.effects,null===r?e.effects= +[h]:r.push(h))}else p={eventTime:p,lane:r,tag:h.tag,payload:h.payload,callback:h.callback,next:null},null===l?(n=l=p,k=m):l=l.next=p,g|=r;h=h.next;if(null===h)if(h=e.shared.pending,null===h)break;else r=h,h=r.next,r.next=null,e.lastBaseUpdate=r,e.shared.pending=null}while(1);null===l&&(k=m);e.baseState=k;e.firstBaseUpdate=n;e.lastBaseUpdate=l;b=e.shared.interleaved;if(null!==b){e=b;do g|=e.lane,e=e.next;while(e!==b)}else null===f&&(e.shared.lanes=0);ra|=g;a.lanes=g;a.memoizedState=m}}function Hh(a, +b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;bc?c:4;a(!0);var d=sf.transition;sf.transition= +{};try{a(!1),b()}finally{z=c,sf.transition=d}}function $h(){return sa().memoizedState}function qk(a,b,c){var d=hb(a);c={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ai(a))bi(b,c);else if(c=Eh(a,b,c,d),null!==c){var e=Z();xa(c,a,d,e);ci(c,b,d)}}function ok(a,b,c){var d=hb(a),e={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ai(a))bi(b,e);else{var f=a.alternate;if(0===a.lanes&&(null===f||0===f.lanes)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState, +h=f(g,c);e.hasEagerState=!0;e.eagerState=h;if(ua(h,g)){var k=b.interleaved;null===k?(e.next=e,ef(b)):(e.next=k.next,k.next=e);b.interleaved=e;return}}catch(n){}finally{}c=Eh(a,b,e,d);null!==c&&(e=Z(),xa(c,a,d,e),ci(c,b,d))}}function ai(a){var b=a.alternate;return a===C||null!==b&&b===C}function bi(a,b){zc=Ad=!0;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}function ci(a,b,c){if(0!==(c&4194240)){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function ya(a,b){if(a&& +a.defaultProps){b=E({},b);a=a.defaultProps;for(var c in a)void 0===b[c]&&(b[c]=a[c]);return b}return b}function tf(a,b,c,d){b=a.memoizedState;c=c(d,b);c=null===c||void 0===c?b:E({},b,c);a.memoizedState=c;0===a.lanes&&(a.updateQueue.baseState=c)}function di(a,b,c,d,e,f,g){a=a.stateNode;return"function"===typeof a.shouldComponentUpdate?a.shouldComponentUpdate(d,f,g):b.prototype&&b.prototype.isPureReactComponent?!qc(c,d)||!qc(e,f):!0}function ei(a,b,c){var d=!1,e=cb;var f=b.contextType;"object"===typeof f&& +null!==f?f=qa(f):(e=ea(b)?pb:J.current,d=b.contextTypes,f=(d=null!==d&&void 0!==d)?Nb(a,e):cb);b=new b(c,f);a.memoizedState=null!==b.state&&void 0!==b.state?b.state:null;b.updater=Dd;a.stateNode=b;b._reactInternals=a;d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=e,a.__reactInternalMemoizedMaskedChildContext=f);return b}function fi(a,b,c,d){a=b.state;"function"===typeof b.componentWillReceiveProps&&b.componentWillReceiveProps(c,d);"function"===typeof b.UNSAFE_componentWillReceiveProps&& +b.UNSAFE_componentWillReceiveProps(c,d);b.state!==a&&Dd.enqueueReplaceState(b,b.state,null)}function uf(a,b,c,d){var e=a.stateNode;e.props=c;e.state=a.memoizedState;e.refs={};ff(a);var f=b.contextType;"object"===typeof f&&null!==f?e.context=qa(f):(f=ea(b)?pb:J.current,e.context=Nb(a,f));e.state=a.memoizedState;f=b.getDerivedStateFromProps;"function"===typeof f&&(tf(a,b,f,c),e.state=a.memoizedState);"function"===typeof b.getDerivedStateFromProps||"function"===typeof e.getSnapshotBeforeUpdate||"function"!== +typeof e.UNSAFE_componentWillMount&&"function"!==typeof e.componentWillMount||(b=e.state,"function"===typeof e.componentWillMount&&e.componentWillMount(),"function"===typeof e.UNSAFE_componentWillMount&&e.UNSAFE_componentWillMount(),b!==e.state&&Dd.enqueueReplaceState(e,e.state,null),wd(a,c,e,d),e.state=a.memoizedState);"function"===typeof e.componentDidMount&&(a.flags|=4194308)}function Ub(a,b){try{var c="",d=b;do c+=fj(d),d=d.return;while(d);var e=c}catch(f){e="\nError generating stack: "+f.message+ +"\n"+f.stack}return{value:a,source:b,stack:e,digest:null}}function vf(a,b,c){return{value:a,source:null,stack:null!=c?c:null,digest:null!=b?b:null}}function wf(a,b){try{console.error(b.value)}catch(c){setTimeout(function(){throw c;})}}function gi(a,b,c){c=Pa(-1,c);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){Ed||(Ed=!0,xf=d);wf(a,b)};return c}function hi(a,b,c){c=Pa(-1,c);c.tag=3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){return d(e)}; +c.callback=function(){wf(a,b)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){wf(a,b);"function"!==typeof d&&(null===ib?ib=new Set([this]):ib.add(this));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function ii(a,b,c){var d=a.pingCache;if(null===d){d=a.pingCache=new rk;var e=new Set;d.set(b,e)}else e=d.get(b),void 0===e&&(e=new Set,d.set(b,e));e.has(c)||(e.add(c),a=sk.bind(null,a,b,c),b.then(a,a))}function ji(a){do{var b; +if(b=13===a.tag)b=a.memoizedState,b=null!==b?null!==b.dehydrated?!0:!1:!0;if(b)return a;a=a.return}while(null!==a);return null}function ki(a,b,c,d,e){if(0===(a.mode&1))return a===b?a.flags|=65536:(a.flags|=128,c.flags|=131072,c.flags&=-52805,1===c.tag&&(null===c.alternate?c.tag=17:(b=Pa(-1,1),b.tag=2,fb(c,b,1))),c.lanes|=1),a;a.flags|=65536;a.lanes=e;return a}function aa(a,b,c,d){b.child=null===a?li(b,null,c,d):Vb(b,a.child,c,d)}function mi(a,b,c,d,e){c=c.render;var f=b.ref;Sb(b,e);d=mf(a,b,c,d,f, +e);c=nf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&c&&Ue(b);b.flags|=1;aa(a,b,d,e);return b.child}function ni(a,b,c,d,e){if(null===a){var f=c.type;if("function"===typeof f&&!yf(f)&&void 0===f.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=f,oi(a,b,f,d,e);a=rd(c.type,null,d,b,b.mode,e);a.ref=b.ref;a.return=b;return b.child=a}f=a.child;if(0===(a.lanes&e)){var g=f.memoizedProps;c=c.compare;c=null!==c?c:qc;if(c(g,d)&&a.ref=== +b.ref)return Qa(a,b,e)}b.flags|=1;a=eb(f,d);a.ref=b.ref;a.return=b;return b.child=a}function oi(a,b,c,d,e){if(null!==a){var f=a.memoizedProps;if(qc(f,d)&&a.ref===b.ref)if(ha=!1,b.pendingProps=d=f,0!==(a.lanes&e))0!==(a.flags&131072)&&(ha=!0);else return b.lanes=a.lanes,Qa(a,b,e)}return zf(a,b,c,d,e)}function pi(a,b,c){var d=b.pendingProps,e=d.children,f=null!==a?a.memoizedState:null;if("hidden"===d.mode)if(0===(b.mode&1))b.memoizedState={baseLanes:0,cachePool:null,transitions:null},y(Ga,ba),ba|=c; +else{if(0===(c&1073741824))return a=null!==f?f.baseLanes|c:c,b.lanes=b.childLanes=1073741824,b.memoizedState={baseLanes:a,cachePool:null,transitions:null},b.updateQueue=null,y(Ga,ba),ba|=a,null;b.memoizedState={baseLanes:0,cachePool:null,transitions:null};d=null!==f?f.baseLanes:c;y(Ga,ba);ba|=d}else null!==f?(d=f.baseLanes|c,b.memoizedState=null):d=c,y(Ga,ba),ba|=d;aa(a,b,e,c);return b.child}function qi(a,b){var c=b.ref;if(null===a&&null!==c||null!==a&&a.ref!==c)b.flags|=512,b.flags|=2097152}function zf(a, +b,c,d,e){var f=ea(c)?pb:J.current;f=Nb(b,f);Sb(b,e);c=mf(a,b,c,d,f,e);d=nf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&d&&Ue(b);b.flags|=1;aa(a,b,c,e);return b.child}function ri(a,b,c,d,e){if(ea(c)){var f=!0;ld(b)}else f=!1;Sb(b,e);if(null===b.stateNode)Fd(a,b),ei(b,c,d),uf(b,c,d,e),d=!0;else if(null===a){var g=b.stateNode,h=b.memoizedProps;g.props=h;var k=g.context,n=c.contextType;"object"===typeof n&&null!==n?n=qa(n):(n=ea(c)?pb:J.current,n=Nb(b, +n));var l=c.getDerivedStateFromProps,m="function"===typeof l||"function"===typeof g.getSnapshotBeforeUpdate;m||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||k!==n)&&fi(b,g,d,n);gb=!1;var r=b.memoizedState;g.state=r;wd(b,d,g,e);k=b.memoizedState;h!==d||r!==k||S.current||gb?("function"===typeof l&&(tf(b,c,l,d),k=b.memoizedState),(h=gb||di(b,c,h,d,r,k,n))?(m||"function"!==typeof g.UNSAFE_componentWillMount&&"function"!==typeof g.componentWillMount|| +("function"===typeof g.componentWillMount&&g.componentWillMount(),"function"===typeof g.UNSAFE_componentWillMount&&g.UNSAFE_componentWillMount()),"function"===typeof g.componentDidMount&&(b.flags|=4194308)):("function"===typeof g.componentDidMount&&(b.flags|=4194308),b.memoizedProps=d,b.memoizedState=k),g.props=d,g.state=k,g.context=n,d=h):("function"===typeof g.componentDidMount&&(b.flags|=4194308),d=!1)}else{g=b.stateNode;Fh(a,b);h=b.memoizedProps;n=b.type===b.elementType?h:ya(b.type,h);g.props= +n;m=b.pendingProps;r=g.context;k=c.contextType;"object"===typeof k&&null!==k?k=qa(k):(k=ea(c)?pb:J.current,k=Nb(b,k));var p=c.getDerivedStateFromProps;(l="function"===typeof p||"function"===typeof g.getSnapshotBeforeUpdate)||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==m||r!==k)&&fi(b,g,d,k);gb=!1;r=b.memoizedState;g.state=r;wd(b,d,g,e);var x=b.memoizedState;h!==m||r!==x||S.current||gb?("function"===typeof p&&(tf(b,c,p,d),x=b.memoizedState), +(n=gb||di(b,c,n,d,r,x,k)||!1)?(l||"function"!==typeof g.UNSAFE_componentWillUpdate&&"function"!==typeof g.componentWillUpdate||("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(d,x,k),"function"===typeof g.UNSAFE_componentWillUpdate&&g.UNSAFE_componentWillUpdate(d,x,k)),"function"===typeof g.componentDidUpdate&&(b.flags|=4),"function"===typeof g.getSnapshotBeforeUpdate&&(b.flags|=1024)):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|= +4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),b.memoizedProps=d,b.memoizedState=x),g.props=d,g.state=x,g.context=k,d=n):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),d=!1)}return Af(a,b,c,d,f,e)}function Af(a,b,c,d,e,f){qi(a,b);var g=0!==(b.flags&128);if(!d&&!g)return e&&vh(b,c,!1), +Qa(a,b,f);d=b.stateNode;tk.current=b;var h=g&&"function"!==typeof c.getDerivedStateFromError?null:d.render();b.flags|=1;null!==a&&g?(b.child=Vb(b,a.child,null,f),b.child=Vb(b,null,h,f)):aa(a,b,h,f);b.memoizedState=d.state;e&&vh(b,c,!0);return b.child}function si(a){var b=a.stateNode;b.pendingContext?th(a,b.pendingContext,b.pendingContext!==b.context):b.context&&th(a,b.context,!1);gf(a,b.containerInfo)}function ti(a,b,c,d,e){Qb();Ye(e);b.flags|=256;aa(a,b,c,d);return b.child}function Bf(a){return{baseLanes:a, +cachePool:null,transitions:null}}function ui(a,b,c){var d=b.pendingProps,e=F.current,f=!1,g=0!==(b.flags&128),h;(h=g)||(h=null!==a&&null===a.memoizedState?!1:0!==(e&2));if(h)f=!0,b.flags&=-129;else if(null===a||null!==a.memoizedState)e|=1;y(F,e&1);if(null===a){Xe(b);a=b.memoizedState;if(null!==a&&(a=a.dehydrated,null!==a))return 0===(b.mode&1)?b.lanes=1:"$!"===a.data?b.lanes=8:b.lanes=1073741824,null;g=d.children;a=d.fallback;return f?(d=b.mode,f=b.child,g={mode:"hidden",children:g},0===(d&1)&&null!== +f?(f.childLanes=0,f.pendingProps=g):f=Gd(g,d,0,null),a=sb(a,d,c,null),f.return=b,a.return=b,f.sibling=a,b.child=f,b.child.memoizedState=Bf(c),b.memoizedState=Cf,a):Df(b,g)}e=a.memoizedState;if(null!==e&&(h=e.dehydrated,null!==h))return uk(a,b,g,d,h,e,c);if(f){f=d.fallback;g=b.mode;e=a.child;h=e.sibling;var k={mode:"hidden",children:d.children};0===(g&1)&&b.child!==e?(d=b.child,d.childLanes=0,d.pendingProps=k,b.deletions=null):(d=eb(e,k),d.subtreeFlags=e.subtreeFlags&14680064);null!==h?f=eb(h,f):(f= +sb(f,g,c,null),f.flags|=2);f.return=b;d.return=b;d.sibling=f;b.child=d;d=f;f=b.child;g=a.child.memoizedState;g=null===g?Bf(c):{baseLanes:g.baseLanes|c,cachePool:null,transitions:g.transitions};f.memoizedState=g;f.childLanes=a.childLanes&~c;b.memoizedState=Cf;return d}f=a.child;a=f.sibling;d=eb(f,{mode:"visible",children:d.children});0===(b.mode&1)&&(d.lanes=c);d.return=b;d.sibling=null;null!==a&&(c=b.deletions,null===c?(b.deletions=[a],b.flags|=16):c.push(a));b.child=d;b.memoizedState=null;return d} +function Df(a,b,c){b=Gd({mode:"visible",children:b},a.mode,0,null);b.return=a;return a.child=b}function Hd(a,b,c,d){null!==d&&Ye(d);Vb(b,a.child,null,c);a=Df(b,b.pendingProps.children);a.flags|=2;b.memoizedState=null;return a}function uk(a,b,c,d,e,f,g){if(c){if(b.flags&256)return b.flags&=-257,d=vf(Error(m(422))),Hd(a,b,g,d);if(null!==b.memoizedState)return b.child=a.child,b.flags|=128,null;f=d.fallback;e=b.mode;d=Gd({mode:"visible",children:d.children},e,0,null);f=sb(f,e,g,null);f.flags|=2;d.return= +b;f.return=b;d.sibling=f;b.child=d;0!==(b.mode&1)&&Vb(b,a.child,null,g);b.child.memoizedState=Bf(g);b.memoizedState=Cf;return f}if(0===(b.mode&1))return Hd(a,b,g,null);if("$!"===e.data){d=e.nextSibling&&e.nextSibling.dataset;if(d)var h=d.dgst;d=h;f=Error(m(419));d=vf(f,d,void 0);return Hd(a,b,g,d)}h=0!==(g&a.childLanes);if(ha||h){d=O;if(null!==d){switch(g&-g){case 4:e=2;break;case 16:e=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:e= +32;break;case 536870912:e=268435456;break;default:e=0}e=0!==(e&(d.suspendedLanes|g))?0:e;0!==e&&e!==f.retryLane&&(f.retryLane=e,Oa(a,e),xa(d,a,e,-1))}Ef();d=vf(Error(m(421)));return Hd(a,b,g,d)}if("$?"===e.data)return b.flags|=128,b.child=a.child,b=vk.bind(null,a),e._reactRetry=b,null;a=f.treeContext;fa=Ka(e.nextSibling);la=b;D=!0;wa=null;null!==a&&(na[oa++]=Ma,na[oa++]=Na,na[oa++]=rb,Ma=a.id,Na=a.overflow,rb=b);b=Df(b,d.children);b.flags|=4096;return b}function vi(a,b,c){a.lanes|=b;var d=a.alternate; +null!==d&&(d.lanes|=b);df(a.return,b,c)}function Ff(a,b,c,d,e){var f=a.memoizedState;null===f?a.memoizedState={isBackwards:b,rendering:null,renderingStartTime:0,last:d,tail:c,tailMode:e}:(f.isBackwards=b,f.rendering=null,f.renderingStartTime=0,f.last=d,f.tail=c,f.tailMode=e)}function wi(a,b,c){var d=b.pendingProps,e=d.revealOrder,f=d.tail;aa(a,b,d.children,c);d=F.current;if(0!==(d&2))d=d&1|2,b.flags|=128;else{if(null!==a&&0!==(a.flags&128))a:for(a=b.child;null!==a;){if(13===a.tag)null!==a.memoizedState&& +vi(a,c,b);else if(19===a.tag)vi(a,c,b);else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===b)break a;for(;null===a.sibling;){if(null===a.return||a.return===b)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}d&=1}y(F,d);if(0===(b.mode&1))b.memoizedState=null;else switch(e){case "forwards":c=b.child;for(e=null;null!==c;)a=c.alternate,null!==a&&null===xd(a)&&(e=c),c=c.sibling;c=e;null===c?(e=b.child,b.child=null):(e=c.sibling,c.sibling=null);Ff(b,!1,e,c,f);break;case "backwards":c= +null;e=b.child;for(b.child=null;null!==e;){a=e.alternate;if(null!==a&&null===xd(a)){b.child=e;break}a=e.sibling;e.sibling=c;c=e;e=a}Ff(b,!0,c,null,f);break;case "together":Ff(b,!1,null,null,void 0);break;default:b.memoizedState=null}return b.child}function Fd(a,b){0===(b.mode&1)&&null!==a&&(a.alternate=null,b.alternate=null,b.flags|=2)}function Qa(a,b,c){null!==a&&(b.dependencies=a.dependencies);ra|=b.lanes;if(0===(c&b.childLanes))return null;if(null!==a&&b.child!==a.child)throw Error(m(153));if(null!== +b.child){a=b.child;c=eb(a,a.pendingProps);b.child=c;for(c.return=b;null!==a.sibling;)a=a.sibling,c=c.sibling=eb(a,a.pendingProps),c.return=b;c.sibling=null}return b.child}function wk(a,b,c){switch(b.tag){case 3:si(b);Qb();break;case 5:Ih(b);break;case 1:ea(b.type)&&ld(b);break;case 4:gf(b,b.stateNode.containerInfo);break;case 10:var d=b.type._context,e=b.memoizedProps.value;y(ud,d._currentValue);d._currentValue=e;break;case 13:d=b.memoizedState;if(null!==d){if(null!==d.dehydrated)return y(F,F.current& +1),b.flags|=128,null;if(0!==(c&b.child.childLanes))return ui(a,b,c);y(F,F.current&1);a=Qa(a,b,c);return null!==a?a.sibling:null}y(F,F.current&1);break;case 19:d=0!==(c&b.childLanes);if(0!==(a.flags&128)){if(d)return wi(a,b,c);b.flags|=128}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null,e.lastEffect=null);y(F,F.current);if(d)break;else return null;case 22:case 23:return b.lanes=0,pi(a,b,c)}return Qa(a,b,c)}function Dc(a,b){if(!D)switch(a.tailMode){case "hidden":b=a.tail;for(var c=null;null!== +b;)null!==b.alternate&&(c=b),b=b.sibling;null===c?a.tail=null:c.sibling=null;break;case "collapsed":c=a.tail;for(var d=null;null!==c;)null!==c.alternate&&(d=c),c=c.sibling;null===d?b||null===a.tail?a.tail=null:a.tail.sibling=null:d.sibling=null}}function W(a){var b=null!==a.alternate&&a.alternate.child===a.child,c=0,d=0;if(b)for(var e=a.child;null!==e;)c|=e.lanes|e.childLanes,d|=e.subtreeFlags&14680064,d|=e.flags&14680064,e.return=a,e=e.sibling;else for(e=a.child;null!==e;)c|=e.lanes|e.childLanes, +d|=e.subtreeFlags,d|=e.flags,e.return=a,e=e.sibling;a.subtreeFlags|=d;a.childLanes=c;return b}function xk(a,b,c){var d=b.pendingProps;Ve(b);switch(b.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return W(b),null;case 1:return ea(b.type)&&(v(S),v(J)),W(b),null;case 3:d=b.stateNode;Tb();v(S);v(J);jf();d.pendingContext&&(d.context=d.pendingContext,d.pendingContext=null);if(null===a||null===a.child)pd(b)?b.flags|=4:null===a||a.memoizedState.isDehydrated&&0===(b.flags& +256)||(b.flags|=1024,null!==wa&&(Gf(wa),wa=null));xi(a,b);W(b);return null;case 5:hf(b);var e=ub(xc.current);c=b.type;if(null!==a&&null!=b.stateNode)yk(a,b,c,d,e),a.ref!==b.ref&&(b.flags|=512,b.flags|=2097152);else{if(!d){if(null===b.stateNode)throw Error(m(166));W(b);return null}a=ub(Ea.current);if(pd(b)){d=b.stateNode;c=b.type;var f=b.memoizedProps;d[Da]=b;d[uc]=f;a=0!==(b.mode&1);switch(c){case "dialog":B("cancel",d);B("close",d);break;case "iframe":case "object":case "embed":B("load",d);break; +case "video":case "audio":for(e=0;e\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(c,{is:d.is}):(a=g.createElement(c),"select"===c&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,c);a[Da]=b;a[uc]=d;zk(a,b,!1,!1);b.stateNode=a;a:{g=qe(c,d);switch(c){case "dialog":B("cancel",a);B("close",a);e=d;break;case "iframe":case "object":case "embed":B("load",a);e=d;break; +case "video":case "audio":for(e=0;eHf&&(b.flags|=128,d=!0,Dc(f,!1),b.lanes=4194304)}else{if(!d)if(a=xd(g),null!==a){if(b.flags|=128,d=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.flags|=4),Dc(f,!0),null===f.tail&&"hidden"===f.tailMode&&!g.alternate&&!D)return W(b),null}else 2*P()-f.renderingStartTime>Hf&&1073741824!==c&&(b.flags|= +128,d=!0,Dc(f,!1),b.lanes=4194304);f.isBackwards?(g.sibling=b.child,b.child=g):(c=f.last,null!==c?c.sibling=g:b.child=g,f.last=g)}if(null!==f.tail)return b=f.tail,f.rendering=b,f.tail=b.sibling,f.renderingStartTime=P(),b.sibling=null,c=F.current,y(F,d?c&1|2:c&1),b;W(b);return null;case 22:case 23:return ba=Ga.current,v(Ga),d=null!==b.memoizedState,null!==a&&null!==a.memoizedState!==d&&(b.flags|=8192),d&&0!==(b.mode&1)?0!==(ba&1073741824)&&(W(b),b.subtreeFlags&6&&(b.flags|=8192)):W(b),null;case 24:return null; +case 25:return null}throw Error(m(156,b.tag));}function Bk(a,b,c){Ve(b);switch(b.tag){case 1:return ea(b.type)&&(v(S),v(J)),a=b.flags,a&65536?(b.flags=a&-65537|128,b):null;case 3:return Tb(),v(S),v(J),jf(),a=b.flags,0!==(a&65536)&&0===(a&128)?(b.flags=a&-65537|128,b):null;case 5:return hf(b),null;case 13:v(F);a=b.memoizedState;if(null!==a&&null!==a.dehydrated){if(null===b.alternate)throw Error(m(340));Qb()}a=b.flags;return a&65536?(b.flags=a&-65537|128,b):null;case 19:return v(F),null;case 4:return Tb(), +null;case 10:return cf(b.type._context),null;case 22:case 23:return ba=Ga.current,v(Ga),null;case 24:return null;default:return null}}function Wb(a,b){var c=a.ref;if(null!==c)if("function"===typeof c)try{c(null)}catch(d){G(a,b,d)}else c.current=null}function If(a,b,c){try{c()}catch(d){G(a,b,d)}}function Ck(a,b){Jf=Zc;a=ch();if(Ie(a)){if("selectionStart"in a)var c={start:a.selectionStart,end:a.selectionEnd};else a:{c=(c=a.ownerDocument)&&c.defaultView||window;var d=c.getSelection&&c.getSelection(); +if(d&&0!==d.rangeCount){c=d.anchorNode;var e=d.anchorOffset,f=d.focusNode;d=d.focusOffset;try{c.nodeType,f.nodeType}catch(M){c=null;break a}var g=0,h=-1,k=-1,n=0,q=0,u=a,r=null;b:for(;;){for(var p;;){u!==c||0!==e&&3!==u.nodeType||(h=g+e);u!==f||0!==d&&3!==u.nodeType||(k=g+d);3===u.nodeType&&(g+=u.nodeValue.length);if(null===(p=u.firstChild))break;r=u;u=p}for(;;){if(u===a)break b;r===c&&++n===e&&(h=g);r===f&&++q===d&&(k=g);if(null!==(p=u.nextSibling))break;u=r;r=u.parentNode}u=p}c=-1===h||-1===k?null: +{start:h,end:k}}else c=null}c=c||{start:0,end:0}}else c=null;Kf={focusedElem:a,selectionRange:c};Zc=!1;for(l=b;null!==l;)if(b=l,a=b.child,0!==(b.subtreeFlags&1028)&&null!==a)a.return=b,l=a;else for(;null!==l;){b=l;try{var x=b.alternate;if(0!==(b.flags&1024))switch(b.tag){case 0:case 11:case 15:break;case 1:if(null!==x){var v=x.memoizedProps,z=x.memoizedState,w=b.stateNode,A=w.getSnapshotBeforeUpdate(b.elementType===b.type?v:ya(b.type,v),z);w.__reactInternalSnapshotBeforeUpdate=A}break;case 3:var t= +b.stateNode.containerInfo;1===t.nodeType?t.textContent="":9===t.nodeType&&t.documentElement&&t.removeChild(t.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(m(163));}}catch(M){G(b,b.return,M)}a=b.sibling;if(null!==a){a.return=b.return;l=a;break}l=b.return}x=zi;zi=!1;return x}function Gc(a,b,c){var d=b.updateQueue;d=null!==d?d.lastEffect:null;if(null!==d){var e=d=d.next;do{if((e.tag&a)===a){var f=e.destroy;e.destroy=void 0;void 0!==f&&If(b,c,f)}e=e.next}while(e!==d)}} +function Id(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function Lf(a){var b=a.ref;if(null!==b){var c=a.stateNode;switch(a.tag){case 5:a=c;break;default:a=c}"function"===typeof b?b(a):b.current=a}}function Ai(a){var b=a.alternate;null!==b&&(a.alternate=null,Ai(b));a.child=null;a.deletions=null;a.sibling=null;5===a.tag&&(b=a.stateNode,null!==b&&(delete b[Da],delete b[uc],delete b[Me],delete b[Dk], +delete b[Ek]));a.stateNode=null;a.return=null;a.dependencies=null;a.memoizedProps=null;a.memoizedState=null;a.pendingProps=null;a.stateNode=null;a.updateQueue=null}function Bi(a){return 5===a.tag||3===a.tag||4===a.tag}function Ci(a){a:for(;;){for(;null===a.sibling;){if(null===a.return||Bi(a.return))return null;a=a.return}a.sibling.return=a.return;for(a=a.sibling;5!==a.tag&&6!==a.tag&&18!==a.tag;){if(a.flags&2)continue a;if(null===a.child||4===a.tag)continue a;else a.child.return=a,a=a.child}if(!(a.flags& +2))return a.stateNode}}function Mf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=kd));else if(4!==d&&(a=a.child,null!==a))for(Mf(a,b,c),a=a.sibling;null!==a;)Mf(a,b,c),a=a.sibling}function Nf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?c.insertBefore(a,b):c.appendChild(a); +else if(4!==d&&(a=a.child,null!==a))for(Nf(a,b,c),a=a.sibling;null!==a;)Nf(a,b,c),a=a.sibling}function jb(a,b,c){for(c=c.child;null!==c;)Di(a,b,c),c=c.sibling}function Di(a,b,c){if(Ca&&"function"===typeof Ca.onCommitFiberUnmount)try{Ca.onCommitFiberUnmount(Uc,c)}catch(h){}switch(c.tag){case 5:X||Wb(c,b);case 6:var d=T,e=za;T=null;jb(a,b,c);T=d;za=e;null!==T&&(za?(a=T,c=c.stateNode,8===a.nodeType?a.parentNode.removeChild(c):a.removeChild(c)):T.removeChild(c.stateNode));break;case 18:null!==T&&(za? +(a=T,c=c.stateNode,8===a.nodeType?Re(a.parentNode,c):1===a.nodeType&&Re(a,c),nc(a)):Re(T,c.stateNode));break;case 4:d=T;e=za;T=c.stateNode.containerInfo;za=!0;jb(a,b,c);T=d;za=e;break;case 0:case 11:case 14:case 15:if(!X&&(d=c.updateQueue,null!==d&&(d=d.lastEffect,null!==d))){e=d=d.next;do{var f=e,g=f.destroy;f=f.tag;void 0!==g&&(0!==(f&2)?If(c,b,g):0!==(f&4)&&If(c,b,g));e=e.next}while(e!==d)}jb(a,b,c);break;case 1:if(!X&&(Wb(c,b),d=c.stateNode,"function"===typeof d.componentWillUnmount))try{d.props= +c.memoizedProps,d.state=c.memoizedState,d.componentWillUnmount()}catch(h){G(c,b,h)}jb(a,b,c);break;case 21:jb(a,b,c);break;case 22:c.mode&1?(X=(d=X)||null!==c.memoizedState,jb(a,b,c),X=d):jb(a,b,c);break;default:jb(a,b,c)}}function Ei(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=new Fk);b.forEach(function(b){var d=Gk.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}}function Aa(a,b,c){c=b.deletions;if(null!==c)for(var d=0;de&&(e=g);d&=~f}d=e;d=P()-d;d=(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*Mk(d/1960))-d;if(10a?16:a;if(null===lb)var d=!1;else{a=lb;lb=null;Qd=0;if(0!==(p&6))throw Error(m(331));var e=p;p|=4;for(l=a.current;null!==l;){var f=l,g=f.child;if(0!==(l.flags&16)){var h=f.deletions;if(null!==h){for(var k=0;kP()-Of?wb(a,0):Sf|=c);ia(a,b)}function Ti(a,b){0===b&&(0===(a.mode&1)?b=1:(b=Rd,Rd<<=1,0===(Rd&130023424)&&(Rd=4194304)));var c=Z();a=Oa(a,b);null!==a&&(ic(a,b,c),ia(a,c))}function vk(a){var b=a.memoizedState,c=0;null!==b&&(c=b.retryLane);Ti(a,c)}function Gk(a,b){var c=0;switch(a.tag){case 13:var d=a.stateNode;var e=a.memoizedState;null!==e&&(c=e.retryLane); +break;case 19:d=a.stateNode;break;default:throw Error(m(314));}null!==d&&d.delete(b);Ti(a,c)}function Mi(a,b){return xh(a,b)}function Tk(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.subtreeFlags=this.flags=0;this.deletions=null;this.childLanes=this.lanes=0;this.alternate=null}function yf(a){a= +a.prototype;return!(!a||!a.isReactComponent)}function Uk(a){if("function"===typeof a)return yf(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===ie)return 11;if(a===je)return 14}return 2}function eb(a,b){var c=a.alternate;null===c?(c=pa(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.type=a.type,c.flags=0,c.subtreeFlags=0,c.deletions=null);c.flags=a.flags&14680064;c.childLanes=a.childLanes;c.lanes=a.lanes;c.child= +a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{lanes:b.lanes,firstContext:b.firstContext};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function rd(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)yf(a)&&(g=1);else if("string"===typeof a)g=5;else a:switch(a){case Bb:return sb(c.children,e,f,b);case fe:g=8;e|=8;break;case ee:return a=pa(12,c,b,e|2),a.elementType=ee,a.lanes=f,a;case ge:return a= +pa(13,c,b,e),a.elementType=ge,a.lanes=f,a;case he:return a=pa(19,c,b,e),a.elementType=he,a.lanes=f,a;case Ui:return Gd(c,e,f,b);default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case hg:g=10;break a;case gg:g=9;break a;case ie:g=11;break a;case je:g=14;break a;case Ta:g=16;d=null;break a}throw Error(m(130,null==a?a:typeof a,""));}b=pa(g,c,b,e);b.elementType=a;b.type=d;b.lanes=f;return b}function sb(a,b,c,d){a=pa(7,a,d,b);a.lanes=c;return a}function Gd(a,b,c,d){a=pa(22,a,d,b);a.elementType= +Ui;a.lanes=c;a.stateNode={isHidden:!1};return a}function Ze(a,b,c){a=pa(6,a,null,b);a.lanes=c;return a}function $e(a,b,c){b=pa(4,null!==a.children?a.children:[],a.key,b);b.lanes=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Vk(a,b,c,d,e){this.tag=b;this.containerInfo=a;this.finishedWork=this.pingCache=this.current=this.pendingChildren=null;this.timeoutHandle=-1;this.callbackNode=this.pendingContext=this.context=null;this.callbackPriority= +0;this.eventTimes=we(0);this.expirationTimes=we(-1);this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0;this.entanglements=we(0);this.identifierPrefix=d;this.onRecoverableError=e;this.mutableSourceEagerHydrationData=null}function Vf(a,b,c,d,e,f,g,h,k,l){a=new Vk(a,b,c,h,k);1===b?(b=1,!0===f&&(b|=8)):b=0;f=pa(3,null,null,b);a.current=f;f.stateNode=a;f.memoizedState={element:d,isDehydrated:c,cache:null,transitions:null, +pendingSuspenseBoundaries:null};ff(f);return a}function Wk(a,b,c){var d=3