From 28271158dd109b5c1824bc67bdbe1a7e331bfb78 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Sun, 14 Jun 2020 14:34:37 -0600 Subject: [PATCH] cs: remove experimental JIT database support Caching compiled JIT fragments in a SQLite database did not turn out to be a viable path, so remove partial support for it. JIT mode in general is rarely a good option, but it's at least completely worked out, so left in for now. --- racket/src/cs/Makefile | 3 +- racket/src/cs/README.txt | 121 ++++++---------- racket/src/cs/linklet.sls | 81 ++--------- racket/src/cs/linklet/db.ss | 266 ------------------------------------ 4 files changed, 55 insertions(+), 416 deletions(-) delete mode 100644 racket/src/cs/linklet/db.ss diff --git a/racket/src/cs/Makefile b/racket/src/cs/Makefile index 435f2ea49c..51ff2faa7a 100644 --- a/racket/src/cs/Makefile +++ b/racket/src/cs/Makefile @@ -127,8 +127,7 @@ LINKLET_SRCS = linklet/version.ss \ linklet/performance.ss \ linklet/annotation.ss \ linklet/compress.ss \ - linklet/cross-compile.ss \ - linklet/db.ss + linklet/cross-compile.ss $(BUILDDIR)linklet.$(CSO): linklet.sls $(LINKLET_SRCS) $(LINKLET_DEPS) $(COMPILE_FILE_DEPS) $(COMPILE_FILE) linklet.sls $(LINKLET_DEPS) diff --git a/racket/src/cs/README.txt b/racket/src/cs/README.txt index db55126791..89e47ba859 100644 --- a/racket/src/cs/README.txt +++ b/racket/src/cs/README.txt @@ -1,5 +1,5 @@ -The implementation of Racket on Chez Scheme in this directory is -organized into two layers: +The implementation of Racket on Chez Scheme (Racket CS) in this +directory is organized into two layers: * The immediate directory contains Scheme sources to implement Racket functionality on top of Chez Scheme. It references sibling @@ -20,13 +20,13 @@ build). Requirements ======================================================================== -Building Racket-on-Chez requires both an existing Racket build and -Chez Scheme build. +Building Racket CS requires both an existing Racket build and Chez +Scheme build. -The existing Racket must be "new enough" to build the current -Racket-on-Chez version. In the worst case, it must be exactly the same -version (using the traditional Racket implementation) as the one -you're trying to build. +The existing Racket must be "new enough" to build the current Racket +CS version. In the worst case, it must be exactly the same version +(using the traditional Racket implementation) as the one you're trying +to build. Note: When you use `configure --enable-cs` or similar as described in "../README.txt", then a bootstrapping variant of Racket is built @@ -43,11 +43,11 @@ already), and see "Building Chez Scheme" below for building. Development versus Build ======================================================================== -The Racket-on-Chez implementation can be built and run in two -different ways: development mode for running directly using a Chez -Scheme installation, and build mode for creating a `racket` or -`racketcs` executable that combines Chez Scheme and Racket -functionality into a single executable. +The Racket CS implementation can be built and run in two different +ways: development mode for running directly using a Chez Scheme +installation, and build mode for creating a `racket` or `racketcs` +executable that combines Chez Scheme and Racket functionality into a +single executable. Development Mode ---------------- @@ -80,11 +80,10 @@ makefile makes too many Unix-ish assumptions. Build Mode ---------- -To build a Racket-on-Chez executable, the `configure` script and -makefile in "c" subdirectory are normally used via `configure` and -`make` in the parent directory of this one, as described in -"../README.txt". However, you can use them directly with something -like +To build a Racket CS executable, the `configure` script and makefile +in "c" subdirectory are normally used via `configure` and `make` in +the parent directory of this one, as described in "../README.txt". +However, you can use them directly with something like cd [build] mkdir cs @@ -98,7 +97,7 @@ is a build directory (usually "../build" relative to [here]). The `configure` script accepts flags like `--enable-racket=...` and `--enable-scheme=...` to select an existing Racket and a Chez Scheme -build directory to use for building Racket-on-Chez: +build directory to use for building Racket CS: * By default, the build uses Racket as "[build]/racket/racket3m" and bootstraps bytecode from "[here]/../../collects". @@ -109,12 +108,12 @@ build directory to use for building Racket-on-Chez: * By default, the build looks for a Chez Scheme build directory as "build/ChezScheme". - Building Racket-on-Chez requires a Chez Scheme build directory, not - just a Chez Scheme installation that is accessible as `scheme`. + Building Racket CS requires a Chez Scheme build directory, not just + a Chez Scheme installation that is accessible as `scheme`. -The resulting Racket-on-Chez executable has the suffix "cs". To -generate an executable without the "cs" suffix, supply -`--enable-csdefault` to `configure`. The presence or absence of "cs" +The resulting Racket CS executable has the suffix "cs". To generate an +executable without the "cs" suffix, supply `--enable-csdefault` to +`configure`. The option to select the presence or absence of "cs" also affects the location of ".zo" files. Compilation on Windows does not use the `configure` script in "c". @@ -129,7 +128,7 @@ not already present (in which case `git` must be available). Machine Code versus JIT ======================================================================== -Racket-on-Chez currently supports three compilation modes: +Racket CS currently supports three compilation modes: * Machine-code mode --- The compiled form of a module is machine code generated by compiling either whole linklets (for small enough @@ -141,7 +140,7 @@ Racket-on-Chez currently supports three compilation modes: In development mode or when the "cs" suffix is used for build mode, compiled ".zo" files in this mode are written to a subdirectory of - "compiled" using the Chez Scheme platform name (e.g., "a6osx"). + "compiled" using the Chez Scheme platform name (e.g., "ta6osx"). Set `PLT_CS_COMPILE_LIMIT` to set the maximum size of forms to compile before falling back to interpreted "bytecode". The default @@ -177,18 +176,11 @@ Racket-on-Chez currently supports three compilation modes: compiled ".zo" files in this mode are written to a "cs" subdirectory of "compiled". - S-expression fragments are hashed at compilation time, so that the - hash for each fragment is stored in the ".zo" file. At JIT time, - the hash is used to consult and/or update a cache (implemented as - an SQLite database) of machine-code forms. Set the `PLT_JIT_CACHE` - environment variable to change the cache file, or set the - environment variable to empty to disable the cache. - In development mode or when the "cs" suffix is used for build mode, set the `PLT_ZO_PATH` environment variable to override the path used for ".zo" files. For example, you may want to preserve a normal build while also building in machine-code mode with `PLT_CS_DEBUG` set, in -which case setting `PLT_ZO_PATH` to something like "a6osx-debug" could +which case setting `PLT_ZO_PATH` to something like "ta6osx-debug" could be a good idea. @@ -228,7 +220,7 @@ Development mode is driven by the makefile in this directory. Building -------- -Running `make` will build the Racket-on-Chez implementation. Use `make +Running `make` will build the Racket CS implementation. Use `make expander-demo` to run a demo that loads `racket/base` from source. Use `make setup` (or `make setup-v` for a verbose version) to build @@ -251,8 +243,8 @@ running plain `racket`, where command-line arguments are supplied in Structure --------- -The Racket-on-Chez implementation is in layers. The immediate layer -over Chez Scheme is called "Rumble", and it implements delimited +The Racket CS implementation is organized in layers. The immediate +layer over Chez Scheme is called "Rumble", and it implements delimited continuations, structures, chaperones and impersonators, engines (for threads), and similar base functionality. The Rumble layer is implemented in Chez Scheme. @@ -357,34 +349,7 @@ examples, including loading `racket/base` from source. Dumping Linklets and Schemified Linklets ---------------------------------------- -Set the `PLT_LINKLET_SHOW` environment variable to pretty print each -linklet generated by the expander and its schemified form that is -passed on to Chez Scheme. - -By default, `PLT_LINKLET_SHOW` does not distinguish gensyms that have -the same base name, so the schemified form is not really accurate. Set -`PLT_LINKLET_SHOW_GENSYM` instead (or in addition) to get more -accurate output. - -In JIT mode, the schemified form is shown after a conversion to -support JIT mode. Set `PLT_LINKLET_SHOW_PRE_JIT` to see the -pre-conversion form. Set `PLT_LINKLET_SHOW_JIT_DEMAND` to see forms as -they are compiled on demand. - -In machine-code mode, set `PLT_LINKLET_SHOW_LAMBDA` to see individual -compiled terms when a linklet is not compiled whole; set -`PLT_LINKLET_SHOW_POST_LAMBDA` to see the linklet reorganized around -those compiled parts; and/or set `PLT_LINKLET_SHOW_POST_INTERP` to see -the "bytecode" form. - -Set `PLT_LINKLET_SHOW_CP0` to see the Schemified form of a linklet -after expansion and optimization by Chez Scheme's cp0. - -Set `PLT_LINKLET_SHOW_ASSEMBLY` to see the assembly form of a linklet -after compilation by Chez Scheme. Assembly format uses Chez Scheme's -abstraction of architecture-specific machine instructions (where the -assembly is translated to actual machine code in a fairly -straightforward way). +See "Inspecting Compiler Passes" in the Racket reference manual. Safety and Debugging Mode ------------------------- @@ -395,13 +360,13 @@ If you make changes to files in "rumble", you should turn off You may want to turn on `DEBUG_COMP` in the makefile, so that backtraces provide expression-specific source locations instead of just procedure-specific source locations. Enabling `DEBUG_COMP` makes -the Racket-on-Chez implementation take up twice as much memory and -take twice as long to load. +the Racket CS implementation take up twice as much memory and take +twice as long to load. -Turning on `DEBUG_COMP` affects only the Racket-on-Chez -implementation. To preserve per-expression locations on compiled -Racket code, set `PLT_CS_DEBUG`. See also "JIT versus Machine Code" -for a suggestion on setting `PLT_ZO_PATH`. +Turning on `DEBUG_COMP` affects only the Racket CS implementation. To +preserve per-expression locations on compiled Racket code, set +`PLT_CS_DEBUG`. See also "JIT versus Machine Code" for a suggestion on +setting `PLT_ZO_PATH`. When you change "rumble" or other layers, you can continue to use Racket modules that were previously compiled to ".zo" form... usually, @@ -411,8 +376,8 @@ compatibility. FFI Differences --------------- -Compared to the traditional Racket implementation, Racket-on-Chez's -FFI behaves in several different ways: +Compared to the traditional Racket implementation, Racket CS's FFI +behaves in several different ways: * The `make-sized-byte-string` function always raises an exception, because a foreign address cannot be turned into a byte string whose @@ -425,10 +390,6 @@ FFI behaves in several different ways: is used as a result type, the C result is copied into a fresh byte string. - * The 'atomic-interior allocation mode returns memory that is allowed - to move after the cpointer returned by allocation becomes - unreachable. - * A `_gcpointer` can only refer to the start of an allocated object, and never the interior of an 'atomic-interior allocation. Like traditional Racket, `_gcpointer` is equivalent to `_pointer` for @@ -440,7 +401,9 @@ FFI behaves in several different ways: * Calling a foreign function implicitly uses atomic mode and also disables GC. If the foreign function calls back to Racket, the - callback runs in atomic mode with the GC still disabled. + callback runs in atomic mode with the GC still disabled. Use the + `blocking?` option for a foreign call or callback to adjuse that + behavior. * An immobile cell must be modified only through its original pointer or a reconstructed `_gcpointer`. If it is cast or reconstructed as @@ -534,7 +497,7 @@ configuration: other layers by 30-50%. * `PLT_CS_DEBUG` not set --- an environment variable similar to - `DEBUG_COMP`, but applies to code compiled by Racket-on-Chez. + `DEBUG_COMP`, but applies to code compiled by Racket CS. Effectiveness: Avoids improvement to stack traces, but also avoids increases load time and memory use of Racket programs by as much as diff --git a/racket/src/cs/linklet.sls b/racket/src/cs/linklet.sls index 9141521726..91a1ca94d2 100644 --- a/racket/src/cs/linklet.sls +++ b/racket/src/cs/linklet.sls @@ -137,17 +137,6 @@ n)))) 10000))) - (define no-future-jit-db? (getenv "PLT_NO_FUTURE_JIT_CACHE")) ; => don't calculate key for cache - (define jit-db-path (let ([bstr (environment-variables-ref - (|#%app| current-environment-variables) - (string->utf8 "PLT_JIT_CACHE"))]) - (cond - [(equal? bstr '#vu8()) #f] ; empty value disables the JIT cache - [(not bstr) - (build-path (find-system-path 'addon-dir) - "cs-jit.sqlite")] - [else (bytes->path bstr)]))) - ;; For "main.sps" to select the default ".zo" directory name: (define platform-independent-zo-mode? (not (eq? linklet-compilation-mode 'mach))) @@ -225,7 +214,6 @@ (include "linklet/read.ss") (include "linklet/annotation.ss") (include "linklet/performance.ss") - (include "linklet/db.ss") ;; `compile`, `interpret`, etc. have `dynamic-wind`-based state ;; that need to be managed correctly when swapping Racket @@ -353,28 +341,6 @@ (fx= (char->integer #\e) (bytevector-u8-ref bv 6)) (fx= (char->integer #\z) (bytevector-u8-ref bv 7)))) - (define-values (lookup-code insert-code delete-code) - (let ([get-procs!-maker - (lambda (retry) - (lambda args - (let-values ([(lookup insert delete) (get-code-database-procedures)]) - (set! lookup-code lookup) - (set! insert-code insert) - (set! delete-code delete) - (apply retry args))))]) - (values (get-procs!-maker (lambda (hash) (lookup-code hash))) - (get-procs!-maker (lambda (hash code) (insert-code hash code))) - (get-procs!-maker (lambda (hash) (delete-code hash)))))) - - (define (add-code-hash a) - (cond - [no-future-jit-db? a] - [else - ;; Combine an annotation with a hash code in a vector - (let-values ([(o get) (open-bytevector-output-port)]) - (fasl-write* (cons (version) a) o) - (vector (sha1-bytes (get)) a))])) - (define-record-type wrapped-code (fields (mutable content) ; bytevector for 'lambda mode; annotation or (vector hash annotation) for 'jit mode arity-mask @@ -387,38 +353,17 @@ f (performance-region 'on-demand - (let ([f (if (and (vector? f) - (or (not jit-db-path) - (wrong-jit-db-thread?))) - (vector-ref f 1) - f)]) - (cond - [(bytevector? f) - (let* ([f (code-from-bytevector f)]) - (wrapped-code-content-set! wc f) - f)] - [(vector? f) - (when jit-demand-on? - (show "JIT demand" (strip-nested-annotations (vector-ref f 1)))) - (let* ([hash (vector-ref f 0)] - [code (lookup-code hash)]) - (cond - [code - (let* ([f (eval-from-bytevector code '() 'compile)]) - (wrapped-code-content-set! wc f) - f)] - [else - (let ([code (compile-to-bytevector (vector-ref f 1) '() 'compile)]) - (insert-code hash code) - (let* ([f (eval-from-bytevector code '() 'compile)]) - (wrapped-code-content-set! wc f) - f))]))] - [else - (let ([f (compile* f)]) - (when jit-demand-on? - (show "JIT demand" (strip-nested-annotations (wrapped-code-content wc)))) - (wrapped-code-content-set! wc f) - f)])))))) + (cond + [(bytevector? f) + (let* ([f (code-from-bytevector f)]) + (wrapped-code-content-set! wc f) + f)] + [else + (let ([f (compile* f)]) + (when jit-demand-on? + (show "JIT demand" (strip-nested-annotations (wrapped-code-content wc)))) + (wrapped-code-content-set! wc f) + f)]))))) (define (jitified-extract-closed wc) (let ([f (wrapped-code-content wc)]) @@ -612,9 +557,7 @@ ;; Preserve annotated `lambda` source for on-demand compilation: (lambda (expr arity-mask name) (let ([a (correlated->annotation (xify expr) serializable? sfd-cache)]) - (make-wrapped-code (if serializable? - (add-code-hash a) - a) + (make-wrapped-code a arity-mask (extract-inferred-name expr name))))] [else diff --git a/racket/src/cs/linklet/db.ss b/racket/src/cs/linklet/db.ss deleted file mode 100644 index de8251cb26..0000000000 --- a/racket/src/cs/linklet/db.ss +++ /dev/null @@ -1,266 +0,0 @@ - -;; For now, don't try to use the JIT database from multiple threads -(meta-cond - [(threaded?) - (begin - (define original-thread-id (get-thread-id)) - (define (wrong-jit-db-thread?) - (not (eqv? original-thread-id (get-thread-id)))))] - [else - (define (wrong-jit-db-thread?) #f)]) - -(define (db-error who fmt . args) - (let ([str (string-append (symbol->string who) - ": " - (apply #%format fmt args))]) - (log-message root-logger 'error 'jit-db str #f) - #f)) - -(define (no-db-procedures) - (values (lambda (hash) #f) - (lambda (hash code) (void)) - (lambda (hash) (void)))) - -;; Gets Sqlite3-based lookup, insert, and delete on demand, -;; returning the dummy functions from `no-db-procedures` -;; if something goes wrong setting up the database -(define (get-code-database-procedures) - (with-interrupts-disabled - (guard - (exn [else (db-error 'load "could not load sqlite ~s" - (if (message-condition? exn) - (condition-message exn) - exn)) - (no-db-procedures)]) - (let ([ok (begin - ;; FIXME: look in the Racket "lib" directory, first - (case (system-type) - [(macosx) (load-shared-object "libsqlite3.0.dylib")] - [(windows) (load-shared-object "sqlite3.dll")] - [else (load-shared-object "libsqlite3.so.0")]) - (void))]) - (define SQLITE_OPEN_READONLY #x00000001) - (define SQLITE_OPEN_READWRITE #x00000002) - (define SQLITE_OPEN_CREATE #x00000004) - - (define SQLITE_OK 0) - (define SQLITE_CONSTRAINT 19) - (define SQLITE_ROW 100) - (define SQLITE_DONE 101) - - (define SQLITE_TRANSIENT -1) - - (define memcpy_pp (foreign-procedure "(cs)byte-copy" (uptr iptr uptr iptr iptr) void)) - (define memcpy_bp (foreign-procedure "(cs)byte-copy" (u8* iptr uptr iptr iptr) void)) - (define memcpy_pb (foreign-procedure "(cs)byte-copy" (uptr iptr u8* iptr iptr) void)) - (define memcpy_bb (foreign-procedure "(cs)byte-copy" (u8* iptr u8* iptr iptr) void)) - - (define (memcpy dest src len) - (cond - [(bytevector? dest) - (if (bytevector? src) - (memcpy_bb src 0 dest 0 len) - (memcpy_pb src 0 dest 0 len))] - [else - (if (bytevector? src) - (memcpy_bp src 0 dest 0 len) - (memcpy_pp src 0 dest 0 len))])) - - (define sqlite3_open_v2 - (foreign-procedure "sqlite3_open_v2" - (u8* ; path - uptr ; receives a pointer result - int ; flags - uptr) ; VFS - int)) - - (define sqlite3_prepare_v2 - (foreign-procedure "sqlite3_prepare_v2" - (uptr ; db - uptr ; statement string - int ; statement length - uptr ; ptr to result - uptr) ; ptr to leftover statement string - int)) - - (define sqlite3_step - (foreign-procedure "sqlite3_step" - (uptr) ; statement - int)) - - (define sqlite3_reset - (foreign-procedure "sqlite3_reset" - (uptr) ; statement - int)) - - (define sqlite3_clear_bindings - (foreign-procedure "sqlite3_clear_bindings" - (uptr) ; statement - int)) - - (define sqlite3_finalize - (foreign-procedure "sqlite3_finalize" - (uptr) ; statement - int)) - - (define sqlite3_bind_blob - (foreign-procedure "sqlite3_bind_blob" - (uptr ; statement - int ; parameter index - u8* ; data - int ; length - iptr) ; use SQLITE_TRANSIENT - int)) - - (define sqlite3_column_blob - (foreign-procedure "sqlite3_column_blob" - (uptr ; statement - int) ; column - uptr)) - (define sqlite3_column_bytes - (foreign-procedure "sqlite3_column_bytes" - (uptr ; statement - int) ; column - int)) - - (define sqlite3_errstr - (foreign-procedure "sqlite3_errstr" - (int) - string)) - - (define sqlite3_errmsg - (foreign-procedure "sqlite3_errmsg" - (uptr) ; database - string)) - - (define (errstr r) - (sqlite3_errstr r)) - - (define db - (let ([db-ptr (foreign-alloc (foreign-sizeof 'uptr))]) - (define r - (sqlite3_open_v2 (bytes-append (path->bytes jit-db-path) - '#vu8(0)) - db-ptr - (bitwise-ior SQLITE_OPEN_READWRITE - SQLITE_OPEN_CREATE) - 0)) - (let ([db (foreign-ref 'uptr 0 db-ptr)]) - (foreign-free db-ptr) - (cond - [(= r SQLITE_OK) db] - [else (db-error 'open "failed ~s" (errstr r))])))) - - (define (prepare db stmt-str) - (let* ([stmt (string->utf8 stmt-str)] - [stmt-len (bytevector-length stmt)] - [stmt-copy (foreign-alloc stmt-len)] - [s-ptr (foreign-alloc (foreign-sizeof 'uptr))] - [rest-ptr (foreign-alloc (foreign-sizeof 'uptr))]) - (memcpy stmt-copy stmt stmt-len) - (let ([r (sqlite3_prepare_v2 db - stmt-copy - stmt-len - s-ptr - rest-ptr)]) - (let* ([s (foreign-ref 'uptr 0 s-ptr)] - [rest (foreign-ref 'uptr 0 rest-ptr)]) - (foreign-free stmt-copy) - (cond - [(= r SQLITE_OK) - (cond - [(= rest (+ stmt-copy stmt-len)) - ;; Success - s] - [else - (finalize s) - (db-error 'prepare "more than one statement ~s" stmt-str)])] - [else - (db-error 'prepare "error ~s" (errstr r))]))))) - - (define (finalize s) - (define r (sqlite3_finalize s)) - (unless (= r SQLITE_OK) - (db-error 'finalize "error ~s" (errstr r)))) - - (define (step s result-shape) - (define r (sqlite3_step s)) - (cond - [(= r SQLITE_ROW) - (let loop ([result-shape result-shape] [col 0]) - (case result-shape - [(bytes) - (let* ([blob (sqlite3_column_blob s col)] - [len (sqlite3_column_bytes s col)] - [bstr (make-bytevector len)]) - (memcpy bstr blob len) - bstr)] - [(void ignore-constraint) (void)] - [else - (cond - [else (db-error 'step "unrecognized result format ~s" result-shape)])]))] - [(= r SQLITE_DONE) - #f] - [(and (= r SQLITE_CONSTRAINT) - (eq? result-shape 'ignore-constraint)) - ;; Ignore a constraint failure, because we assume it reflects a - ;; lost race trying to insert code for the same hash - (void)] - [else - (db-error 'step "error ~s" (errstr r))])) - - (define initialized-db - (when db - (let ([s (prepare db "SELECT name FROM sqlite_master WHERE type='table' AND name='compiled'")]) - (unless (step s 'void) - (let ([s2 (prepare db "CREATE TABLE compiled (hash blob(24), code blob(1024), PRIMARY KEY (hash))")]) - (step s2 'void) - (finalize s2))) - (finalize s)) - ;; FIXME: this pragma is needed for reasonable performance on Linux, but - ;; we should instead batch updates in `insert` (since it's ok for an - ;; update to get lost, but not ok for the database to be corrupted) - (let ([s (prepare db "PRAGMA synchronous = OFF")]) - (step s 'void) - (finalize s)))) - - (define (check who r) - (unless (= r SQLITE_OK) - (db-error who "error ~s" (errstr r)))) - - (define (bind s pos v) - (check 'bind (sqlite3_bind_blob s pos v (bytevector-length v) SQLITE_TRANSIENT))) - - (define lookup-s (prepare db "SELECT code FROM compiled WHERE hash=$1")) - (define delete-s (prepare db "DELETE FROM compiled WHERE hash=$1")) - (define insert-s (prepare db "INSERT INTO compiled VALUES ($1, $2)")) - - (define (reset s) - (sqlite3_reset s) ; ignore any error, since it's a repeat of recent error - (check 'clear-bindings (sqlite3_clear_bindings s))) - - (define (lookup hash) - (with-interrupts-disabled - (bind lookup-s 1 hash) - (let ([r (step lookup-s 'bytes)]) - (reset lookup-s) - r))) - - (define (insert hash code) - (with-interrupts-disabled - (bind insert-s 1 hash) - (bind insert-s 2 code) - (step insert-s 'ignore-constraint) - (reset insert-s) - (void))) - - (define (delete hash) - (with-interrupts-disabled - (bind delete-s 1 hash) - (step delete-s 'void) - (reset delete-s) - (void))) - - (if db - (values lookup insert delete) - (no-db-procedures))))))