<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: собачья будка</title>
    <description>The latest articles on DEV Community by собачья будка (@scumdograscalhouse).</description>
    <link>https://dev.to/scumdograscalhouse</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F346225%2F9b596e5b-8392-479f-85c3-be8dfa7d455a.jpeg</url>
      <title>DEV Community: собачья будка</title>
      <link>https://dev.to/scumdograscalhouse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/scumdograscalhouse"/>
    <language>en</language>
    <item>
      <title>как сконвертировать пачку изображений в webp</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 30 Mar 2022 07:14:26 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/kak-skonviertirovat-pachku-izobrazhienii-v-webp-4noo</link>
      <guid>https://dev.to/scumdograscalhouse/kak-skonviertirovat-pachku-izobrazhienii-v-webp-4noo</guid>
      <description>&lt;p&gt;для этого понадобится либа &lt;a href="https://developers.google.com/speed/webp/docs/cwebp"&gt;cwebp&lt;/a&gt;, которая принимает на вход степень сжатия (1..100), имя входного и выходного файлов, и простой баш цикл.&lt;br&gt;
соберем все картинки в одну директорию и перейдем в нее, после чего опишем скрипт:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for file in *
do
cwebp -q 100 "$file" -o "${file%.png}.webp"
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>как наложить блюр в фигме</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 30 Mar 2022 07:12:41 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/kak-nalozhit-bliur-v-fighmie-4kg5</link>
      <guid>https://dev.to/scumdograscalhouse/kak-nalozhit-bliur-v-fighmie-4kg5</guid>
      <description>&lt;p&gt;для формы блюра используем любую фигуру (&lt;code&gt;R&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh39pe1vkgdijxek7p0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foh39pe1vkgdijxek7p0x.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;и выделим необходимое для замутнения место:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy593my7ip2eh1zzno8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgy593my7ip2eh1zzno8m.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;изменим цвет заливки на белый с полупрозрачностью в 1%:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5qxl8h3s9pzbs5dzhl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5qxl8h3s9pzbs5dzhl8.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;и добавим эффект &lt;code&gt;background blur&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjdjwrjofp867nsh8izn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjdjwrjofp867nsh8izn.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;клик на иконку позволяет настраивать степень блюра. well done!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie1qb891f801ol0spu5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie1qb891f801ol0spu5a.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>генерируем xlsx из rss фида</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Sun, 27 Mar 2022 06:22:06 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/ghienieriruiem-xlsx-iz-rss-fida-4fpo</link>
      <guid>https://dev.to/scumdograscalhouse/ghienieriruiem-xlsx-iz-rss-fida-4fpo</guid>
      <description>&lt;p&gt;нагулял задачу генерации таблиц из rss фида, парсеры выходят на арену. план такой: из терминала кормим скрипт набором аргументов с линком к rss фиду и его настройками, добавляем прогресс бары для отзывчивости, на выход отдаем сгенерированную таблицу.&lt;/p&gt;

&lt;p&gt;сперва набросаем пачку зависимостей:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add rss-parser
yarn add --dev exceljs moment posthtml progress request request-promise yargs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;за дело! импортирую переменные и определяю парсер:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const ExcelJS = require('exceljs')
const Parser = require('rss-parser')
const posthtml = require('posthtml')
const rp = require('request-promise')
const moment = require('moment')
const ProgressBar = require('progress')
const yargs = require("yargs")

const parser = new Parser()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;описываю обязательные и не очень аргументы, определяю входную функцию и на ходу рассказываю о прогрессе в терминал:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const options = yargs
    .usage(`Usage: -f &amp;lt;rss uri&amp;gt;`)
    .option('f', {
        alias: 'feed',
        describe: 'RSS feed uri',
        type: 'string',
        demandOption: true
    })
    .option('a', {
        alias: 'amount',
        describe: 'Needed RSS feed posts amount',
        type: 'string'
    })
    .option('n', {
        alias: 'outputFileName',
        describe: 'XLS output file name',
        type: 'string'
    })
    .option('o', {
        alias: 'cellOptions',
        describe: 'Sheet cell additional options',
        type: 'array'
    })
    .argv

process.stdout.write(`great options, bruh, let's start already!\n`)

entry(options.feed, options.amount, options.outputFileName, options.cellOptions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;главная функция будет принимать на вход обязательный линк на фид и необязательные количество постов, имя таблицы на выходе и набор кастомных настроек для ячеек. назначаю нужные столбцы и их ключи:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let entry = async (rssFeed, amount = 5, outputFileName = 'result', cellOptions = []) =&amp;gt; {
    process.stdout.write(`parsing your rss feed...\n`)
    let feed = await parser.parseURL(rssFeed)

    process.stdout.write(`creating excel workbook...\n`)
    const workbook = new ExcelJS.Workbook()
    const worksheet = workbook.addWorksheet(outputFileName)
    worksheet.columns = [{
            header: 'text',
            key: 'col_text'
        },
        {
            header: 'url',
            key: 'col_url'
        },
        {
            header: 'images',
            key: 'col_images'
        },
        {
            header: 'time',
            key: 'col_time'
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;приступаю к генерации новых строк и добавляю тикающий прогресс бар:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process.stdout.write(`generating posts from rss feed...\n`)
let generatedRows = await generatePostsMetaFromFeed(feed, amount)
let generatedRowsBar = new ProgressBar('[:bar] :current/:total table rows generated\n', {
    incomplete: ' ',
    complete: '#',
    total: generatedRows.length
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;функция &lt;code&gt;generatePostsMetaFromFeed&lt;/code&gt; займется пирсингом элементов фида и генерацией набора с нужными таблице полями:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let convertFeedToPosts = feed =&amp;gt; [...feed.items.map(item =&amp;gt; item.link)] // для пагинации по страницам фида понадобятся линки

let generatePostsMetaFromFeed = async (feed, amount) =&amp;gt; {
    let res = []

    let posts = []
    let feedLink = feed.link

    if (amount &amp;gt; 10) {
        process.stdout.write(`wow, so much posts? taking care of it...\n`)
        let pages = Math.round(amount / 10) // пагинация для доступа к последующим страницам фида
        let pagesLoadingBar = new ProgressBar('[:bar] :current/:total processed\n', {
            incomplete: ' ',
            complete: '#',
            total: pages
        })

        posts.push(...convertFeedToPosts(feed))

        process.stdout.write(`loading needed pages...\n`)
        for (let i = 2; i &amp;lt;= pages; i++) {
            await rp(encodeURI(`${feedLink}?feed=rss&amp;amp;paged=${i}`))
                .then(async rssPage =&amp;gt; {
                    let parsedRSSFeed = await parser.parseString(rssPage)
                    let isLastPage = i === pages

                    if (isLastPage) {
                        let modItems = parsedRSSFeed.items.filter((_, index) =&amp;gt; index &amp;lt; amount % 10)

                        posts.push(...convertFeedToPosts({
                            items: modItems
                        }))
                    } else {
                        posts.push(...convertFeedToPosts(parsedRSSFeed))
                    }

                    pagesLoadingBar.tick()
                })
                .catch(err =&amp;gt; {
                    console.error('huh, rss pagination failed', err.code)
                })
        }
    } else {
        process.stdout.write(`not a lot of posts, gonna be quick!\n`)
        posts.push(...convertFeedToPosts({
            items: feed.items.slice(0, amount)
        }))
    }

    process.stdout.write(`time to generate some text for our table!\n`)
    let postsHandlingBar = new ProgressBar('[:bar] :current/:total posts handled\n', {
        incomplete: ' ',
        complete: '#',
        total: posts.length
    })

    for (let i = 0; i &amp;lt; posts.length; i++) {
        let postLink = posts[i]
        let title, description, image

        await rp(postLink)
            .then(html =&amp;gt; {
                process.stdout.write(`wuush, working on it...\n`)
                posthtml().use(tree =&amp;gt; { // парсим дерево и только нужные таблице значения нод
                    tree.match({
                        tag: 'title'
                    }, node =&amp;gt; {
                        title = node.content[0]
                    })
                    tree.match({
                        attrs: {
                            name: 'description'
                        },
                        tag: 'meta'
                    }, node =&amp;gt; {
                        description = node.attrs.content
                    })
                    tree.match({
                        attrs: {
                            property: 'og:image'
                        },
                        tag: 'meta'
                    }, node =&amp;gt; {
                        image = node.attrs.content
                    })
                }).process(html)

                postsHandlingBar.tick()
            })
            .catch(err =&amp;gt; {
                console.error('huh, post parsing failed', err)
            })

        res.push({
            title,
            description,
            image,
            link: postLink
        })
    }

    return res
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;строки сгенерированы, пора вернуться во входную &lt;code&gt;entry&lt;/code&gt; функцию и прикрутить их к инстансу &lt;code&gt;worksheet&lt;/code&gt;, используя метод &lt;code&gt;addRow&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process.stdout.write(`making some rows for your sheet...\n`)
for (let i = 0; i &amp;lt; generatedRows.length; i++) {
    let {
        title,
        description,
        image,
        link
    } = generatedRows[i]
    let columnText = `${title}\n\n${description}\n\n${link}`

    if (cellOptions.length) {
        cellOptions.forEach(cOption =&amp;gt; {
            if (cOption === 'noImage') {
                image = ''
            }
            if (cOption === 'noOGCard') {
                link = ''
            }
        })
    }

    worksheet.addRow({
        col_text: columnText,
        col_url: link,
        col_images: image,
        col_time: moment().add(i, 'days').format('DD/MM/YYYY hh:mm').toString()
    })

    generatedRowsBar.tick()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;lift off! теперь можно отдавать таблицу:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process.stdout.write(`creating your ${outputFileName} file...\n`)
await workbook.xlsx.writeFile(`${outputFileName}.xlsx`)
    .then(() =&amp;gt; {
        process.stdout.write(`${outputFileName} created allright!\n`)
    })
    .catch((err) =&amp;gt; {
        process.stdout.write('huh, creating error: ', err)
    })

process.stdout.write(`all done, love!\n`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;таблица в кармане, profit!&lt;/p&gt;

&lt;p&gt;исходный код: &lt;a href="https://github.com/arkatriymfalnaya/xlsx-from-rss-generator"&gt;https://github.com/arkatriymfalnaya/xlsx-from-rss-generator&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>очередной слайдер, которого никто не просил</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Sat, 26 Mar 2022 06:17:52 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/ochieriednoi-vielosipied-na-moiem-balkonie-c77</link>
      <guid>https://dev.to/scumdograscalhouse/ochieriednoi-vielosipied-na-moiem-balkonie-c77</guid>
      <description>&lt;p&gt;натыкаясь на вакансии от разных контор, я периодически берусь за тестовые задания, чтобы поразвлечься. в этот раз выбор пал на, кто бы сомневался, очередной слайдер. ну и пусть.&lt;/p&gt;

&lt;p&gt;собираем проект, пара библиотек нам пригодится:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init
yarn add path express pug path
yarn add --dev nodemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;сразу добавим &lt;code&gt;dev&lt;/code&gt; скрипт в &lt;code&gt;package.json&lt;/code&gt;, который будет стучаться к нашему сервер-скрипту:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
    "dev": "nodemon src/server.js"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;создадим папку &lt;code&gt;src&lt;/code&gt; и файл &lt;code&gt;server.js&lt;/code&gt;, в котором опишем наш сервер и движок для pug шаблонов:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express')
const path = require('path')
const fs = require('fs')

const app = express()
const port = '8000'

app.listen(port, () =&amp;gt; {
    console.log(`Listening on http://localhost:${port}`)
})

app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'pug')
app.use(express.static(path.join(__dirname, 'public')))

app.get('/', (_, res) =&amp;gt; {
    try {
        let paths = fs.readdirSync(path.join(__dirname + '/public/images/')) // там будут храниться наши картинки для слайд-шоу
        let combinedPaths = paths.map(p =&amp;gt; `images/${p}`)

        res.render('index', {
            imagesPaths: combinedPaths
        })
    } catch (err) {
        throw err
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;самое время набросать разметку. одна центрирующая обертка, одна секция самого слайдера, контролы и сами слайды, которые отдаются серверным скриптом:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doctype html
html(lang='en')
  head
    meta(charset='utf-8')
    link(rel='stylesheet' href='styles/index.css')
    meta(name='viewport', content='width=device-width, initial-scale=1, shrink-to-fit=no')
  body
    main.wrapper
        section.slider
            each src in imagesPaths
                article.slider__item.slider__item--appear.slide
                    figure
                        img(src=src alt='pic')
            section.slider__controls
                button
                button
    script(type='module' src='scripts/index.js')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;переходим к самому вкусному - к интерактивности, которую возьмет на себя скрипт &lt;code&gt;scripts/index.js&lt;/code&gt;. сперва обезопасим себя и дождемся загрузки DOM дерева:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('DOMContentLoaded', () =&amp;gt; {
  …
}, false)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;вытащим из дерева нужные ноды, объявим переменные для интервала и учёта текущего индекса слайда:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const slidesList = document.querySelectorAll('.slider__item')
const controlsList = document.querySelector('.slider__controls').children
const leftControl = controlsList[0]
const rightControl = controlsList[1] // больше двух детей не предполагается

let interval = null
let currentIndex = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;в конце скрипта инициализируем события и рендеринг самого слайдера:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('keydown', e =&amp;gt; {
    if (e.key === 'ArrowLeft') moveSlider('left') // переключение левой стрелкой
    if (e.key === 'ArrowRight') moveSlider('right') // переключение правой стрелкой
    if (e.key === ' ' &amp;amp;&amp;amp; e.target.nodeName !== 'BUTTON') {
        if (interval) {
            stopSliding() // остановка на текущем слайде по нажатию на пробел
        } else {
            proceedSliding() // продолжение слайд-шоу, если была остановка
        }
    }
})
leftControl.addEventListener('click', () =&amp;gt; moveSlider('left'), false)
rightControl.addEventListener('click', () =&amp;gt; moveSlider('right'), false)

initSlider(slidesList)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;столько функций еще не написано! надо исправлять:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const initSlider = slides =&amp;gt; {
    if (currentIndex &amp;gt; slides.length) currentIndex = 1
    if (currentIndex &amp;lt; 1) currentIndex = slides.length

    updateSlider(slidesList)
}

const moveSlider = to =&amp;gt; {
    if (to === 'right') updateCurrentIndex('right')
    else updateCurrentIndex('left')

    initSlider(slidesList)
}

…

const stopSliding = () =&amp;gt; {
    clearInterval(interval)
    interval = null // очищаем интервал, чтобы остановить слайд-шоу

    slidesList[currentIndex - 1].classList.add('slide--stopped')
}

const proceedSliding = () =&amp;gt; {
    interval = setInterval(() =&amp;gt; {
        updateCurrentIndex('right')
        initSlider(slidesList)
    }, 3000)
    slidesList[currentIndex - 1].classList.remove('slide--stopped')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;многие участки дублируются, потому выносим их в отдельные хендлеры:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const updateSlider = slides =&amp;gt; {
    clearInterval(interval)

    for (let i = 0; i &amp;lt; slides.length; i++) {
        slides[i].classList.add('slider__item--inactive') // стили класса будут удалять слайд из разметки
        slides[i].classList.remove('slide--stopped')
    }

    slides[currentIndex - 1].classList.remove('slider__item--inactive')

    interval = setInterval(() =&amp;gt; { // переопределяем интервал, ведь слайд-шоу не просто так шоу
        updateCurrentIndex('right')
        initSlider(slidesList)
    }, 3000)
}

…

const updateCurrentIndex = to =&amp;gt; {
    if (to === 'right') currentIndex++
    else currentIndex--
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ой, почти все! добавим стили и анимации для появления слайдов:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* {
    box-sizing: border-box;
}

body, section, figure, button {
    padding: 0;
    margin: 0;
}

.wrapper {
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(0, 0, 0, 1);
}

.slider {
    position: relative;
    width: 100%;
    height: 540px;
}

.slider__views-counter {
    position: absolute;
    top: -28px;
    left: 0;
    margin: 0;
    margin-left: 14px;
    font-family: 'Helvetica';
    font-size: 20px;
    line-height: 24px;
    color: white;
}

.slider__item {
    display: flex;
    height: 100%;
}

.slider__item--inactive {
    display: none;
}

.slider__item--appear {
    animation-name: slide-appearing;
    animation-duration: 1.5s;
}

.slide {
    align-items: center;
}

.slide--stopped {
    outline: 5px auto Highlight;
}

.slide figure {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}

.slide figure img {
    max-width: 100%;
    width: auto;
    max-height: 100%;
    height: auto;
}

.slider__controls {
    position: absolute;
    z-index: 2;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    margin: auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}

.slider__controls button {
    position: relative;
    width: 14%;
    max-width: 80px;
    height: 40%;
    border: none;
    background: transparent;
    cursor: pointer;
    border-radius: 8px;
    background-color: rgba(255, 255, 255, .14);
}

.slider__controls button:hover {
    background-color: rgba(255, 255, 255, .18);
}

.slider__controls button:active {
    box-shadow: 0px 0px 22px rgba(0, 0, 0, 0.2);
}

.slider__controls button::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    margin: auto;
    width: 60%;
    height: 60%;
    background-image: url(/icons/arrow.svg);
    background-size: 100% 100%;
    background-repeat: no-repeat;
    filter: invert();
}

.slider__controls button:nth-child(2)::after {
    transform: rotate(180deg);
}

@keyframes slide-appearing {
    from {
        opacity: .2;
    }
    to {
        opacity: 1;
    }
}

@media screen and (min-width: 1020px) {
    .slider {
      width: 80%;
      max-width: 1020px;
    }

    .slider__views-counter {
        margin-left: 0;
    }

    .slider__controls button {
        max-width: 40px;
    }
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;пора бы глянуть, чего уже там нарисовалось то. запустим &lt;code&gt;yarn dev&lt;/code&gt; и перейдем по &lt;code&gt;http://localhost:8000&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M6dHxp48--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/516kolihee5dharkglw6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M6dHxp48--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/516kolihee5dharkglw6.gif" alt="Image description" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;а что, дизайн вышел покурить? ну и пусть, ведь важнее, что внутри.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>как я избавлялся от экстремизма в блоге</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Thu, 24 Mar 2022 13:28:44 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/kak-ia-ot-ekstriemizma-izbavlialsia-3f3d</link>
      <guid>https://dev.to/scumdograscalhouse/kak-ia-ot-ekstriemizma-izbavlialsia-3f3d</guid>
      <description>&lt;p&gt;по мотивам недавних судебных решений блог моей компании о соцсетях и вокруг попадал под длящееся правонарушение. неопределенность по поводу нужных действий от других компаний и отсутствие решения суда сбивало с толку, потому, прежде чем скрывать все статьи с упоминаниями продуктов компании M**a, было решено подстраховаться иноагентскими методами. &lt;/p&gt;

&lt;p&gt;первым вариантом был скрипт, которому можно скормить статьи и регуляркой добавить сноски всем упоминаниям экстремистских продуктов. с этой идеи начал, этой идеей и закончил.&lt;/p&gt;

&lt;p&gt;wordpress хранит статьи в базе, доступ из админки по вкладке &lt;code&gt;записи&lt;/code&gt;. встроенные инструменты позволяют экспортировать и импортировать контент из базы в удобном &lt;code&gt;xml&lt;/code&gt; формате, осталось разобраться со структурой. для парсинга разогнал библиотеку &lt;code&gt;fast-xml-parser&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const data = fs.readFileSync(path.join(__dirname, fileName), 'utf8')
const parser = new XMLParser()

let jObj = parser.parse(data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;рассмотрев дерево, выделил нужные мне ветки: &lt;code&gt;wp:postmeta&lt;/code&gt; и &lt;code&gt;content:encoded&lt;/code&gt;, дело за малым. пишем простую регулярку, выискивающую подстроку, которая не начинается с символа &lt;code&gt;[&lt;/code&gt; (используется для markdown тегов), имеет одно вхождение из набора названий запрещенных продуктов &lt;code&gt;(название1|название2|название3)&lt;/code&gt; и любое окончание для русской вариации &lt;code&gt;[а-яА-Я]*&lt;/code&gt;. для туллтипов использовался wordpress плагин &lt;a href="https://getshortcodes.com/docs/"&gt;Shortcodes Ultimate&lt;/a&gt;. поскольку реплейсить придется для нескольких веток, выносим эту исторую в отдельную функцию и обрабатываем корнер кейсы:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let replaceWithToolbar = str =&amp;gt; str.replace(/[^\[|\/](meta|instagram|facebook|инстаграм|мета|фейсбук)[а-яА-Я]*/gi, (subStr, _, subStrIndex) =&amp;gt; {
  let nextSymbol = str[subStrIndex + subStr.length]

  if(str[subStrIndex - 3] + str[subStrIndex - 2] + str[subStrIndex - 1] + str[subStrIndex] === 'www.') return subStr

  let space = '&amp;lt;code style="letter-spacing: -7px;"&amp;gt; &amp;lt;/code&amp;gt;'

  let start = subStr[0]
  let end = nextSymbol === ' ' ? space : ''
  let updatedStr = subStr.substring(1)

  let tooltipText =
    subStr.includes('нстагра') || subStr.includes('ейсб') || subStr.includes('nstagr') || subStr.includes('aceboo')
      ? 'Продукт принадлежит организации, признанной экстремистской на территории Российской Федерации.'
      : 'Организация признана экстремистской на территории Российской Федерации.'

  return `${start}${space}[su_tooltip text="${tooltipText}" text_align="center"]${updatedStr}[/su_tooltip]${end}`
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;остается только грязно обновить исходные ветки на получившиеся:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let metasArray = jObj.rss.channel.item['wp:postmeta']
let newMetasArray = metasArray.map(m =&amp;gt; {
  if(m['wp:meta_key'] === '_crb_description' || m['wp:meta_key'] === '_crb_short_description') {
    let newMeta = replaceWithToolbar(m['wp:meta_value'])
    m['wp:meta_value'] = newMeta
  }

  return m
})
jObj.rss.channel.item['wp:postmeta'] = newMetasArray

let content = jObj.rss.channel.item['content:encoded']
let newContent = replaceWithToolbar(content)
jObj.rss.channel.item['content:encoded'] = newContent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;после чего билдим дерево в новый xml и пишем в файл:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const builder = new XMLBuilder({ processEntities:false })
const xmlContent = builder.build(jObj)
fs.writeFileSync(path.join(__dirname, `output_${fileName}`), xmlContent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ну, вроде подстраховались!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bUZudF_E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nieiz68ro34yviib8bwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bUZudF_E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nieiz68ro34yviib8bwe.png" alt="Image description" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;исходный код: &lt;a href="https://github.com/arkatriymfalnaya/avoid-extremism"&gt;https://github.com/arkatriymfalnaya/avoid-extremism&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>architectural styles and the design of network-based software architectures. representational state transfer [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:43 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/architectural-styles-and-the-design-of-network-based-software-architectures-representational-state-transfer-3jde</link>
      <guid>https://dev.to/scumdograscalhouse/architectural-styles-and-the-design-of-network-based-software-architectures-representational-state-transfer-3jde</guid>
      <description>&lt;p&gt;Вольный перевод главы диссертации Roy Thomas Fielding "&lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm"&gt;Representational State Transfer (REST)&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Representational State Transfer (REST)
&lt;/h1&gt;

&lt;p&gt;Эта глава представляет и конкретизирует Representational State Transfer (REST) - архитектурный стиль для распределенных гипермедийных систем, описывает принципы разработки программного обеспечения, следуя REST, а также описывает ограничения взаимодействия для сохранения этих принципов, в то же время сопоставляя их с ограничениями других архитектурных стилей. REST - это гибридный стиль, полученный из описанных в &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm"&gt;третьей лаве&lt;/a&gt; архитектурных стилей и определяющий единый интерфейс соединителя коннектора в сочетании с дополнительными ограниченияеми. Структура архитектуры ПО из &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/software_arch.htm"&gt;первой главы&lt;/a&gt; используется для определения архитектурных элементов REST изучения примеров просессов, коннекторов и представлений данных прототипных архитектур.&lt;/p&gt;

&lt;h2&gt;
  
  
  5.1 Процесс выведения REST
&lt;/h2&gt;

&lt;p&gt;Обоснование проектирования, лежащее в основе веб архитектуры, может быть описано архитектурым стилем, который состоит из набора применяемых к элементам ограничений. Изучая влияние каждого ограничения по мере го добавления к развивающемуся стилю, мы может определять свойства, индуцированные ограничениями веба. Затем для формирования нового архитектурного стиля, который лучше отражает свойства современной веб архитектуры, могут быть применены дополнительные ограничения. Эта секция предоставляет общий обзор REST путём рассмотрения процесса выведения его как архитектурного стиля. Конкретные ограничения, из которых состоит REST, будут описаны в последующих разделах  .&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.1 Начиная с Нулевого Стиля
&lt;/h3&gt;

&lt;p&gt;Как для построек, так и для программного обеспечения существует два общих взгляда на процесс архитектурного проектирования. Первый: проектировщик начинает с пустого состояния, рисовальной или чертежной доски и, пока потребности предполагаемой системы не будут удовлетворены, выстраивает архитектуру из уже знакомых компонентов. Второй: проектировщик отталкивается от потребностей системы без ограничений, после чего постепенно определяет и применяет ограничения к её элементам, чтобы разграничить область проектирования и позволить силам, влияющим на поведение системы, течь естественным образом, в гармонии с системой. Первый взгляд подразумевает креативное и неограниченное видение, второй - сдержанность и понимание контекста нужной системы. REST был разработан с помощью последнего. Фигуры с 5-1 по 5-8 графически описывают, как примененные ограничения разграничивают процесс представления архитектуры в качестве инкрементального набора ограничений.&lt;/p&gt;

&lt;p&gt;Нулевой стиль (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_1"&gt;5-1&lt;/a&gt;) - это просто пустой набор ограничений. С точки зрения архитектуры, нулевой стиль описывает систему, в которой четкая граница между компонентами не установлена. Это начальная точка для нашего описания REST.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LCc0cvAG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/null_style.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LCc0cvAG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/null_style.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/null_style.gif" width="435" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.2 Клиент-сервер
&lt;/h3&gt;

&lt;p&gt;Первыми добавленными к нашему гибридному стилю ограничениями будут ограничения клиент-серверного архитектурного стиля (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_2"&gt;5-2&lt;/a&gt;),  описанные в  &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_1"&gt;Секции 3.4.1&lt;/a&gt;. Принцип, лежащий в основе клиент-серверных ограничений, заключается в разделении интересов. Разделением интересов пользовательского интерфейса от интересов хранилища данных, мы улучшаем портативность пользовательского интерфейса на несколько платформ и улучшаем масшабируемость, упрощая серверные компоненты. Для веба наиболее важным, пожалуй, является то, что разделение позволяет компонентам развиваться независимо, тем самым поддерживая требования расширяемости интернета для нескольких организационных доменов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aDKB2JCN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/client_server_style.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aDKB2JCN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/client_server_style.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/client_server_style.gif" width="435" height="81"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.3 Отсутствие состояния (Stateless)
&lt;/h3&gt;

&lt;p&gt;Следующее ограничение клиент-серверного взаимодействия: коммуникация должна не иметь состояния по своей природе, следуя стилю client-stateless-server (CSS) из &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_3"&gt;Секции 3.4.3&lt;/a&gt; (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_3"&gt;5-3&lt;/a&gt;), что подразумевает, что каждый запрос от клиента к серверу должен содержать всю необходимую для его понимания информацию и не может использовать хранящийся на сервере контекст. Потому состояние сессии полностью хранится на клиенте.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ppq0QdGA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/stateless_cs.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ppq0QdGA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/stateless_cs.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/stateless_cs.gif" width="435" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Это ограничение индуцирует свойства видимости, надежности и расширяемости. Видимость улучшается за счет того, что системе мониторинга не нужно выходить за пределы данных запроса, чтобы определить его полную природу. Надежность улучшается, потому что облегчается задача по восстановлению после частичных сбоев. Расширяемость улучшается, потому что отсуствие необходимости хранения состояния между запросами позволяет серверному компоненту быстрее освобождать ресурсы и еще больше упрощает реализацию, потому что серверу не приходится управлять использованием ресурсов между запросами.&lt;/p&gt;

&lt;p&gt;Как и большинство архитектурных решений, ограничение в виде отсутствия состояния является компромиссом проектирования. Недостатком может быть то, что этот способ может снизить производительность сети из-за увеличения количества повторяющихся данных (per-interaction overhead), отправляемых в сериях запросов, поскольку эти данные нельзя оставлять на сервере в общем контексте. К тому же, хранение состояния приложения на стороне клиента уменьшает возможность контроля за поведением приложения со стороны сервера, поскольку приложение становится зависимым от правильности реализации семантики для нескольких версияй клиента.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.4 Кэш
&lt;/h3&gt;

&lt;p&gt;Для того, чтобы улучшить эффективность сети, мы добавляем ограничения кэша для формирования стиля client-cache-stateless-server из &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_4"&gt;секции 3.4.4&lt;/a&gt; (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_4"&gt;5-4&lt;/a&gt;). Ограничения кэша требуют, чтобы данные внутри ответа на запрос были явно или неявно отмечены, как кэшируемые или некэшируемые. Если ответ кэшируемый - клиентскому кэшу дается право переиспользовать данные ответа для последующих эквивалентных запросов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---YersdjC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/ccss_style.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---YersdjC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/ccss_style.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/ccss_style.gif" width="435" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Преимущество от добавления ограничения кэша в том, что появляется возможность частично или полностью исключить некоторые взаимодействия за счет уменьшения средней задержки их серии, тем самым улучшая эффективность, расширяемость и воспринимаемую пользователем производительность. Компромисс, однако, заключается в том, что кэш может снизить надежность, если устаревшие данные в кэше значительно отличаются от данных, полученных при отправке запроса на непосредственно сервер.&lt;/p&gt;

&lt;p&gt;Ранняя веб архитектура, как показано на изображении &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_5"&gt;5-5&lt;/a&gt;, определялась client-cache-stateless-server набором ограничений. Таким образом, для обмена статическими документами через интернет представленное для веб архитектуры до 1994 обоснование проектирования было сфокусировано на клиент-серверном взаимодействии без состояния. Протоколы для взаимного взаимодействия имели рудиментарную поддержку для кэшей без общего доступа, но не ограничивали интерфейс согласованным набором семантики для всех ресурсов. Вместо того, в вебе использовалась общая библиотека клиент-серверной реализации (CERN libwww) для обеспечения согласованности между веб приложениями.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sdaae-qi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/early_web_arch.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sdaae-qi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/early_web_arch.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/early_web_arch.gif" width="433" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Разработчики веб реализаций уже превзошли ранние методы проектирования. Кроме статических документов, запросы могли идентифицировать сервисы, которые динамически генерировали ответы, вроде image-maps [Кевин Хьюз] и server-side scripts [Роб МакКул]. Также была начата работа над промежуточными компонентами в виде прокси-серверов и общих кэшей, но для надежной связи те нуждались в расширених протоколов. Следующие разделы описывают ограничения, которые были добавлены к архитектурному стилю веба, для указания расширений, формирующих современную веб архитектуру.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.5 Унифицированный интерфейс
&lt;/h3&gt;

&lt;p&gt;Главной отличительной чертой REST стиля от остальных является, акцент на единый интерфейс между компонентами (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_6"&gt;5-6&lt;/a&gt;). Применение такого принципа к интерфейсу компонент улучшает видимость взаимодействий и позволяет упростить архитектуру системы в целом. Реализации отделены от сервисов, которые они предоставляют, что способствует их независимому развитию. Компромисс в том, что унифицированный интерфейс снижает эффективность, поскольку информацию передается в стандартизированном виде, а не в виде, определенном под нужды приложения. Интерфейс REST был спроектирован для эффективной передачи крупного количества гипермедиа данных, частого случая в вебе, но не оптимален для других видов архитектурного взаимодействия.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_cjAYFVz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/uniform_ccss.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_cjAYFVz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/uniform_ccss.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/uniform_ccss.gif" width="435" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Чтобы достчиь унифицированного интерфейса, на поведение компонентов должны быть наложены некоторые архитектурные ограничения. REST определяется четырьмя интерфейсными ограничениями: идентификация ресурсов; управление ресурсами через представления; самоописывающиеся сообщения; и гипермедиа, как двигатель состояния приложения. Эти ограничения будут обсуждены в &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_2"&gt;секции 5.2&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.6 Многоуровневая система
&lt;/h3&gt;

&lt;p&gt;Для последующего улучшения поведения и требований расширяемости интернета, мы добавили ограчения уровневой системы (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_7"&gt;5-7&lt;/a&gt;). Как описано в &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_4_2"&gt;секции 3.4.2&lt;/a&gt;, уровневый системный стиль позволяет архитектуре состоять их иерархичных слоев путем особого поведения компонентов - каждый компонент не может "смотреть" за пределы уровня, с которым взаимодействует. Ограничивая знание о системе до одного уровня, мы обеспечиваем подложке незавимость и ограничиваем сложность системы в целом. Уровни могут быть использованы, чтобы оборачивать legacy сервисы и огорождать  от них новые, упрощая компоненты за счет перемещения редкоиспользуемых функций в общий промежуточный уровень. Такие уровни также могут использоваться для улучшения расширяемости системы, обеспечивая балансировку нагрузки служб между несколькими сетями и процессорами.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t8tpyspC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/layered_uccss.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t8tpyspC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/layered_uccss.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/layered_uccss.gif" width="435" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Главным недостатом уровненой системы является то, что они увеличивают расходы и, соответстно, задержку при обработке данных, снижая воспринимаемую пользователем производительность. Для сетевой системы, поддерживающей ограничения кэширования, эти издержки можно компенсировать преимуществами общего кэширования в промежуточном уровне. Размещение общиъ кэшей на границах организационного домена может привести к значительному повышению производительности. Такие уровни также позволяют применять политики безопасности к данным, пересекающим эти границы, как того требуют файрволлы.&lt;/p&gt;

&lt;p&gt;Комбинация многоуровневой системы и ограничений унифицированного интерфейса приводит архитектурные свойства к виду pipe-and-filter (секция &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_2_2"&gt;3.2.2&lt;/a&gt;). Хотя REST взаимодействие является двусторонним, потоки данных большого размера гипермедиа взаимодействия могут обрабатываться как сеть потоков данных с фильтрующими компонентами, выборочно примеяемыми к потокам для преобразования их содержимого. Внутри REST промежуточные компоненты могут активно преобразовывать содержимое сообщений, потому что сообщения самоописывающиеся и их семантика видна на промежуточных уровнях.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.7 Код по требованию
&lt;/h3&gt;

&lt;p&gt;Последним дополнением к нашему набору ограничений будет  стиль code-on-demand из секции &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/net_arch_styles.htm#sec_3_5_3"&gt;3.5.3&lt;/a&gt; (Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_8"&gt;5-8&lt;/a&gt;). REST позволяет клиенту расширяться функциональности клиента и выполнять код в виде апплетов или скриптов. Это упрощает клиентов, уменьшая количество необходимых для предварительной реализации фич. Благодаря возможности загрузки фич после деплоя, улучшается расширяемость системы. Однако, видимость также уменьшается, потому это ограничение внутри REST является опциональным.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k7qvbjCZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/rest_style.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k7qvbjCZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/rest_style.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_style.gif" width="435" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Понятие опционального ограничения может показаться оксюмороном. Тем не менее, оно имеет предназначение при проектировании системы, охватывающей несколько организационных границ. Это значит, что, когда опциональное ограничение действует только для некоторой области системы, архитектура получает преимущество. Например, если все клиентское ПО внутри организации поддерживает Java-апплеты, то сервисы в этой организации могут быть сконструированы таким образом, чтобы они получали преимущества расширенной функциональности с помощью загружаемых классов Java. Однако, файрволл организации может не допустить передачу Java-апплетов из внешних источников, и, следовательно, для остальной части Интернета это будет выглядеть так, как будто эти клиенты не поддерживают код по требованию. Опциональное ограничение позволяет нам проетировать архитектуру, которая в общих случаях поддерживает желаемое поведение, но может быть отключена в некоторых контекстах.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1.8 Summary
&lt;/h3&gt;

&lt;p&gt;REST состоит из набора рахитектурных ограничений, выбранных из-за присущих им свойст. В то же время, каждое из этих ограничений можно рассматривать отдельно, а описание их с точки зрения происхождения из общих архитектурных стилей облегчает понимание обоснования их выбора. Изображение &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_9"&gt;5-9&lt;/a&gt; отображает ответвления ограничений REST в терминах сетевых архитектурных стилей, описанных в главе 3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--owfFNToa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/rest_derivation.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--owfFNToa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/rest_derivation.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_derivation.gif" width="435" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5.2 Архитектурные элементы REST
&lt;/h2&gt;

&lt;p&gt;Стиль REST является абстракцией архитектурных элементов внутри распределенной гипермедийной системы. REST игнорирует детали реализации компонента и синтаксис протокола для лучшего фокусирования на ролях компонентов, их интерпретации значимых элементов данных, ограничений их взаимодействия с другими компонентами. REST охватывает фундаментальные ограничения компонентов, коннекторов и данных, которые определяют основу веб архитектуры и, таким образом, сущность его поведения как сетевого приложения.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.1 Элементы данных
&lt;/h3&gt;

&lt;p&gt;В отличие от распределенного объектного стиля, где все данные скрываются внутри компонентами обработки, природа и состояние архитектурных элементов данных - ключевой аспект  REST.  Обоснование такого проектирования можно увидеть в природе самих распределенных гипермедиа. Когда выбирается ссылка, информация должна переместиться  из места хранения в место, где она будет использоваться пользователем. В отличие от многих других парадигм распределенной обработки, где возможно и обычно более эффективно перемещать сам «агент обработки» (например, мобильный код, хранимую процедуру, выражение поиска и т. д.) к данным, а не перемещать данные в процессор.&lt;/p&gt;

&lt;p&gt;Распределенная гипермедийная архитектура имееть только три основных свойства: 1) отрисовка данных в месте их хранения и отправка получателю изображений фиксированного формата; 2) инкапсуляция данных с помощью рендер движка и отправка обоих получателю; 3) отправка raw данных получателю вместе с метаданными, описывающими тип данных, чтобы получатель мог выбрать свой собственный движок рендеринга.&lt;/p&gt;

&lt;p&gt;Каждой свойство имеет как преимущества, так и недостатки. Первое свойство, традиционно клиент-серверного стиля, позволяет всей информации о настоящей природе данных оставаться скрытой внутри отправителя, упрощая реализацию клиента и предотвращая неточные суждения о структуре данных. Однако, оно также серьезно ограничивает возможности получателя и накладывает большую часть нагрузки на отправителя, что может привести к проблемам расширяемости. Второе свойство, стиля мобильного объекта, предоставляет сокрытие информации, позволяя особым образом обрабатывать данные с помощью уникального движка рендеринга, но ограничивает возможность получателя определить, чего нужно ожидать от движка рендеринга и может значительно увеличить объем передаваемых данных. Третье свойство позволяет отправителю оставаться простым и расширяемым, минимизируя передаваемые байты, но теряет преимущества сокрытия информации и обязывает отправителя и получателя понимать одинаковые типы данных.&lt;/p&gt;

&lt;p&gt;REST предоставляет гибрид этих трех свойств, фокусируясь на совместном понимании типов данных с метаданными, но ограничивает объем раскрываемых для стандартизированного интерфейса данных. REST компоненты взаимодействую между собой путем передачи представления ресурса в формате, соответсвующем одному из развивающихся наборов стандартных типов данных, отобранных динамически на основе природы самого ресурса и возможностей или требований получателя. Представление будет скрываться за интерфейсом внезавимости от того, находится ли оно в том же формате, что и необработанный источник, или получено из него. Преимущества стиля мобильного объекта достигаются посредством отправки представления, состоящего из инструкций в стандартном формате данных инкапсулированного движка рендеринга (например, как в Java). Потому REST достигает разделения интересов клиент-серверного стиля, не мешая расширяемости системы, и позволяет скрывать информацию через основной интерфейс, чтобы обеспечить инкапсуляцию и эволюцию сервисов, и обеспечивает разнообразный набор функций через загружаемые движки.&lt;/p&gt;

&lt;p&gt;Элементы данных REST обобщены в таблице &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#tab_5_1"&gt;5-1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/fd530e9f1c8a444fb5a02fc431323627"&gt;Untitled&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.1.1 Ресурсы и идентификаторы ресурса
&lt;/h3&gt;

&lt;p&gt;Ключевой асбтракцией информации в REST является &lt;em&gt;ресурс&lt;/em&gt;. Любая информация, которая может быть названа, может являться ресурсом: документ или изображение, временный сервис (например, "погода в Лос Анджелес на сегодня"), коллекция других ресурсов, невиртуальный объект (например, человек) и так далее. Другими словами, любой концепция, которая может быть целью гипертекстовой ссылки автора, должна попадать под определение ресурса. Ресурс - это концептуальное отображение набора сущностей, а не сущности, которая соответствует сопоставлению в любой конкретный момент времени.  &lt;/p&gt;

&lt;p&gt;Точнее, ресурс &lt;em&gt;R&lt;/em&gt; является изменяющейся функцией &lt;em&gt;M*R&lt;/em&gt;(t)&lt;em&gt;, которая за время *t&lt;/em&gt; mapsотображает набор сущностей или значений, который эквивалентны. Значения в наборе могут быть &lt;em&gt;представлениями ресурсов&lt;/em&gt; и/или &lt;em&gt;идентификаторами ресурсов&lt;/em&gt;. Ресурс может отображаться на пустой набор, который позволяет создавать ссылки на концепцию перед любой реализацией этой концепции - понятие, которое было чуждо большинству гипертекстовых систем до появления веба. Некоторые ресурсы являются статическими в том смысле, что при рассмотрении в любое время после их создания они всегда соттветствуют одному и тому же набору значений. Другие имеют высокую степень расхождений в занчениях с тчечением времени. Единственное, что требуется для статинчости ресурса - семантика, поскольку именно она отличает один ресурс от другого.&lt;/p&gt;

&lt;p&gt;Например, "предпочтительной версией авторов" академической статьи является отображение, значение которого меняется со временем, тогда как "статьи, опубликованные в материалах конференции X" статичны. Это два разных ресурса, даже если они оба отображают одно и то же значение в определенный момент времени. Различие необходимо для того, чтобы оба ресурса можно было идентифицировать и ссылаться независимо. Аналогичным примером из разработки будет являться раздельный идентификатор файла исходного кода с управлением версиями при образении к "последней версии", "версии 1.2.7" или "версии, включенной в релиз Orange".&lt;/p&gt;

&lt;p&gt;Это абстрактное определение ресурса включает ключевые особенности веб архитектуры. Первое: оно обеспечивает универсальность, охватывая множество источников информации без искусственного их различения по типу или реализации. Второе: оно допускает позднюю привязку ссылки к представлению, позволяя осуществлять согласование содержимого на основе характеристик запроса. И, наконец, позволяет автору ссылаться на концепцию, а не на какое-то единичное представление этой концепции, тем самым устраняя необходимость менять все существующие ссылки при изменении представления (при условии, что автор использовал правильный идентификатор).&lt;/p&gt;

&lt;p&gt;REST использует идентификатор ерсурса для идентификации конкретного ресурса, участвующего во взаимодействии между компонентами. REST коннекторы предоставляют общий интерфейс для доступа к набору значений ресурса и манипулирования им независимо от того, как определена функция членства или от типа программного обеспечения, которое обрабатывает запрос. Инструмент, который назначил идентификатор ресурса, позволяющий ссылаться на ресурс, отвечает за поддержание семантической достоверности отображения с течением времени (т.е. гарантирует, что функция членства не изменяется).&lt;br&gt;
Традиционные гипертекстовые системы, которые обычно действуют в закрытых или локальных окружениях, используеют уникальные идентификаторы узов или документов, которые изменяются сразу после изменения информации, полагаясь на серверы ссылок, чтобы поддреживать отдельно ссылки от контента. Поскольку централизованные серверы ссылок - это анафема для огромного масштаба и многопрофильных сетевых требований, вместо этого REST полагается на то, что автор выбирает идентификатор ресурса, который наилучшим образом соответствует природе идентифицируемой концепции. Естественно, качество идентификатора часто пропорционально сумме денег, потраченной на сохранение его валидности, что приводит к разрыву ссылок, когда эфемерная (или плохо поддерживаемая) информация перемещается или исчезает со временем.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.1.2 Представления
&lt;/h3&gt;

&lt;p&gt;REST компоненты выполняют действия с ресурсом, используя представление для захвата текущего или предполагаемого состояния этого ресурса и передачи этого представления между компонентами.. Представление - это последовательность байтов плюс метаданные представления для их описания. Другие часто используемые, но менее точные имена для представления включают: документ, файл, и экземпляр или сущность HTTP сообщения.&lt;/p&gt;

&lt;p&gt;Представление состоит из данных, метаданных, описывающих данные, и, иногда, метаданных для описания метаданных (обычно для проверки целостности сообщения). Метаданные представлены в форме пар имя-значение, где имя соответствует стандарту, который определяет структуру и семантику значения. Сообщения ответов могут включать как метаданные представления, так и метаданные ресурса: информацию о ресурсе, которая не является специфичной для поставляемого представления.&lt;/p&gt;

&lt;p&gt;Между компонентами управляющие данные определяют цель сообщения, например запрашиваемое действие или значение ответа. Он также используется для параметризации запросов и переопределения поведения некоторых дефолтных соединительных элементов. Например, поведение кэша может быть изменено управляющими данными, включенными в сообщение запроса или ответа.&lt;/p&gt;

&lt;p&gt;В зависимости от данных управления сообщениями данное представление может указывать текущее состояние запрошенного ресурса, желаемое состояние запрошенного ресурса или значение некоторого другого ресурса, такого как представление входных данных в форме запроса клиента, или представление некоторого состояния ошибки для ответа. Например, удаленная разработка ресурса требует, чтобы автор отправлял представление на сервер, устанавливая таким образом значение для этого ресурса, которое может быть получено последующими запросами. Если набор значений ресурса в данный момент времени состоит из нескольких представлений, согласование контента может использоваться для выбора наилучшего представления для включения в данное сообщение.&lt;/p&gt;

&lt;p&gt;Формат данных представления известен как медиа тип.  Представление может быть включено в сообщение и обработано получателем в соответствии с контрольными данными сообщения и характером типа носителя. Некоторые типы мультимедиа предназначены для автоматической обработки, некоторые предназначены для просмотра пользователем, а некоторые способны на оба. Составные типы мультимедиа могут использоваться для включения нескольких представлений в одно сообщение.&lt;/p&gt;

&lt;p&gt;Конструкция мультимедийного типа может напрямую влиять на воспринимаемую пользователем производительность распределенной гипермедиа-системы. Любые данные, которые должны быть получены до того, как получатель сможет начать рендеринг, увеличивают задержку взаимодействия. Формат данных, который помещает наиболее важную информацию рендеринга вперед, так что исходная информация может быть постепенно увеличена, пока остальная информация принимается, приводит к гораздо лучшей воспринимаемой пользователем производительности, чем формат данных, который должен быть полностью получен до рендеринг может начаться.&lt;/p&gt;

&lt;p&gt;Например, веб-браузер, который может постепенно визуализировать большой HTML-документ во время его получения, обеспечивает значительно лучшую воспринимаемую пользователем производительность, чем та, которая ожидает, пока весь документ полностью не будет получен перед рендерингом, даже если производительность сети одинакова. Обратите внимание, что на возможность визуализации представления также может повлиять выбор контента. Если размеры динамически изменяемых таблиц и встроенных объектов должны быть определены до того, как они могут быть отображены, их появление в области просмотра страницы гипермедиа увеличит ее задержку.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.2 Коннекторы
&lt;/h3&gt;

&lt;p&gt;REST использует различные типы коннекторов, приведенные в таблице &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#tab_5_2"&gt;5-2&lt;/a&gt;, для инкапсулирования доступа к ресурсам и передачи их представлений. Коннекторы представляют собой абстрактный интерфейс для взаимодействия компонентов, упрощая его четким разделением проблем и сокрытием базовой реализации ресурсов и механизма связи. Универсальность интерфейса также обеспечивает заменяемость: если пользователь имеет доступ к системе только с помощью абстрактного интерфейса, реализация омжет быть заменена без влияния на пользователей. Поскольку коннектор управляет сетевым взаимодействием для компонента, информация может быть доступна через несколько взаимодействий для улучшения эффективности и отзывчивости. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/909a8531ecde4ff28fe605eb91bd2561"&gt;Untitled&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Все REST взаимодействия не имею состояния. То есть, каждый запрос содержит всю необходимую информацию для того, чтобы коннектор понял запрос вне зависимости от любых запросов, которые могли предшествовать ему. Это ограничение выполняет четыре функции: 1) устраняет необходимость коннекторам сохранять состояние между запросами, что, тем самым, сокращает потребление физических ресурсов и улучшает расширяемость; 2) позволяет обрабатывать взаимодействия параллельно, не требуя, чтобы механизм обработки понимал семантику взаимодействия; 3) позволяет посреднику просматривать и понимать запрос изолированно, что может быть необходимо, если сервисы перестраиваются динамически; и 4) заставляет всю информацию, которая может влиять на переиспользуемость кэшированного ответа присутствовать в каждом запросе.&lt;/p&gt;

&lt;p&gt;Интерфейс коннектора аналогичен процедурному вызову, но с несколькими важными различиями в передаче параметров и результатов. Входные параметры состоят из данных управления запросом, идентификатора указывающего на цель запроса ресурса  и необязательного представления, выходные - из данных управления ответом, необязательных метаданных ресурса и необязательного представления. С абстрактной точки зрения вызов является синхронным, но оба вида параметров могут передаваться как потоки данных. Другими словами, обработка может вызываться перед тем, как значения параметров будут известны полностью, что позволяет избегать задержек пакетной обработки больших передач данных.&lt;/p&gt;

&lt;p&gt;Основными типами коннектора является клиент и сервер. Существенная разница между этимти двумя в том, что клиент, отправляя запрос, инициирует взаимодействие, а сервер прослушивает соединения и отвечает на запросы. Компонент может содержать коннекторы как клиента, так и сервера.&lt;/p&gt;

&lt;p&gt;Третий тип коннетора - коннектор кэша - может располагаться на интерфейсе клиента или сервера для сохранения кэшируемых ответов на текущие взаимодействия, чтобы их можно было повторно использовать для последующих запрошенных взаимодействий. Кэш может использоваться клиентом для избежания повторного сетевого взаимодействия, или сервером для избежания птовроного процесса генерации ответа, причем оба случая служат для уменьшения задержки взаимодействия. Кэш обычно реализуется в адресном пространстве соединителя, которые его использует.&lt;/p&gt;

&lt;p&gt;Некоторые кэш коннекторы являются общими, что означает, что его кэшированные ответы могут использоваться в ответе клиенту, отличному от того, для которого ответ был получен первоначально. Такое совместное кэширование может быть эффективным для снижения влияния "flash crowds" на нагруженный сервер, особенно когда кэширование организовано иерархически для охвата больших групп пользователей, таких как пользователи внутренней сети компании, клиенты интернет-услуг, провайдера или университеты, разделяющие магистраль национальной сети. Однако, совместное кэширование также может приводить к ошибкам, если кэшированный ответ не совпадает с тем, что было бы получено новым запросом. REST пытается сбалансировать стремление к прозрачности в поведении кэша со стремлением к эффективнмоу использованию сети, а не предполагать, что абсолютная рпозрачность требуется всегда.&lt;/p&gt;

&lt;p&gt;Кэш может определять кэшируемость запроса, поскольку интерфейс является общим, а не специфическим для каждого ресурса. По умолчанию, ответ на запрос поиска кэшируется, а ответы на другие запросы нет. Если какая-либо форма аутентификации пользователя является частью запроса, или если ответ указывает, что он не должен использоваться совместно, тогда ответ может быть кэширован только в кэш общего пользования. Компонент может переопределять эти значения по умолчанию, включив управляющие данные которые помечают взаимодействие как кэшируемое, не кэшируемое или кэшируемое только в течение ограниченного времени. &lt;/p&gt;

&lt;p&gt;Средство распознавания преобразует частичные или полные идентификаторы ресурса в информацию о сетевом аддресе, необходимую для устноавления межкомпонентного соединения. например, большинство URI включают в себя имя хоста DNS в качетсве механизма идентификации полномочий ресурса по его именованию. Чтобы инициировать запрос, веб браузер извлечет имя хоста из URI и использует распознаватель DNS для получения адреса интернет-протокола. Другой пример: некоторые схемы идентификации (например, URN) требуют, чтобы посредник преобразовывал постоянный идентификатор в более переходный адрес для доступа к идентифицированному ресурсу. Использование одного или нескольких промежуточных распознавателей может увеличить долговечность ссылок на ресурсы за счет косвенного обращения, хотя это увеличить задержку запроса.&lt;/p&gt;

&lt;p&gt;Последним типом коннектора является туннель, который просто передает взаимодействия через границу соединения, такую как межсетевой экран или сетевой шлюз более низкого уровня. Единственная причина, по которой он моделируется как часть REST и не абстрагируется как часть сетевой инфраструктуры, в том, что некоторые REST компоненты из поведения активного компонента на поведение туннеля могут переключаться динамически. Основным примером является HTTP прокси, который переключается на туннель в ответ на метод CONNECT запроса, что позволяетс его клиенту напрямую связываться с удаленным сервером используя другом протокол, такой как TLS, который не допускает прокси. Такой туннель исчезает, когда оба конца соединения прекращают связь.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2.3 Компоненты
&lt;/h3&gt;

&lt;p&gt;REST компнонеты, выведенные в таблице &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#tab_5_3"&gt;5-3&lt;/a&gt;, типизируются на основе принимаемых ими ролей в событиях приложения.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/d2aff6c030994616a76b6f26fbdd2a0c"&gt;Untitled&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;User agent использует клиентский коннектор для инициирования запроса и становится окончательным его получателем. Основной пример - веб браузер, который предоставляет доступ к информационным сервисам и отрисовывает ответы сервисов в соответсвии с потребностями приложения.&lt;/p&gt;

&lt;p&gt;Origin server использует серверный коннектор для управления пространством имен для запрашиваемого ресурса. Является окончательным источником для представления своих ресурсов и должен быть окончательным получаетел любого запроса, если тот требует изменения значений своих ресурсов. Каждый origin server предоставляет общий интерфейс для своих сервисов в виде иерархии ресурсов. Детали реализации ресурса сокрыты за интерфейсом.&lt;/p&gt;

&lt;p&gt;Для пересылки запросов и ответов промежуточные компоненты ведут себя и как клиент, и как сервер. Компонент proxy является посредником, выбранным клиентом для обеспечения интерфейса инкапсуляции для своих сервисов, трансляции данных, повышения производительности и защиты безопасности. Шлюзовый (a.k.a., reverse proxy) компонент - посредник, навязанный сетевым или исходным сервером для обеспечения инкапсуляции интерфейса других служб, для перевода данных, повышения производительности или обеспечения безопасности. Заметьте, что разница между proxy и шлюзом в том, что клиенту нужно явно определить, когда ему нужно использовать proxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  5.3 Архитектурные представления REST
&lt;/h2&gt;

&lt;p&gt;Теперь, когда у нас есть понимание архитектурных элементов REST в отдельности, мы можем использовать архитектурные представления, чтобы описать, как элементы работают вместе, чтобы сформировать архитектуру. Три типа представлений - процесс, коннектор и данные - полезны для освещения принципов проектирования REST.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3.1 Представление процесса
&lt;/h3&gt;

&lt;p&gt;Процессное представление архитектуры в первую очередь эффективно для выявления взаимосвязей взаимодействия между компонентами, показывая путь данных, когда они проходят через систему. Процессное представление архитектуры в первую очередь эффективно для выявления взаимосвязей взаимодействия между компонентами, показывая путь данных, когда они проходят через систему. На изображении &lt;a href="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#fig_5_10"&gt;5-10&lt;/a&gt; приведен пример представления процесса из архитектуры на основе REST в конкретном случае во время обработки трех параллельных запросов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R40_cCBM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/rest_process_view.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R40_cCBM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://www.ics.uci.edu/%257Efielding/pubs/dissertation/rest_process_view.gif" alt="https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_process_view.gif" width="435" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Разделение интересов клиента и сервера REST упрощает реализацию компонентов, снижает сложность семантики коннекткоров, повышает эффективность настройки производительности и масштабируемость компонентов чистого сервера. Многоуровневые системные ограничения позволяют вводить посредников - прокси, шлюзы и межсетевые экраны - в различных точках обмена данными, не изменяя интерфейсы между компонентами, что позволяет им помогать в преобразовании обмена данными или повышать производительность посредством крупномасштабного общего кэширования. REST позволяет выполнять промежуточную обработку, ограничивая сообщения для самоописания: взаимодействие не требует состояния между запросами, стандартные методы и типы мультимедиа используются для указания семантики и обмена информацией, а ответы явно указывают на кешируемость.&lt;/p&gt;

&lt;p&gt;Поскольку компоненты подключаются динамически, их расположение и функции для конкретного действия приложения имеют характеристики, аналогичные стилю конвейера (pipe) и фильтра. Хотя компоненты REST обмениваются данными через двунаправленные потоки, обработка каждого направления независима и поэтому восприимчива к преобразователям потока (фильтрам). Общий интерфейс соединителя позволяет размещать компоненты в потоке на основе свойств каждого запроса или ответа.&lt;/p&gt;

&lt;p&gt;Сервисы могут быть реализованы с использованием сложной иерархии посредников и нескольких серверов распределенного происхождения. Природа REST без сохранения состояния позволяет каждому взаимодействию быть независимым от других, устраняя необходимость в понимании общей топологии компонентов, невыполнимую задачу для архитектуры масштаба веба и позволяя компонентам выступать в качестве пунктов назначения или посредников, определяемых динамически по цели каждого запроса. Коннекторы должны знать о существовании друг друга только во время их взаимодействия, хотя они могут кэшировать существование и возможности других компонентов по соображениям производительности.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3.2 Представление коннектора
&lt;/h3&gt;

&lt;p&gt;Представление архитектуры соединителя концентрируется на механике связи между компонентами. Для архитектуры на основе REST нас особенно интересуют ограничения, которые определяют общий интерфейс ресурсов.&lt;/p&gt;

&lt;p&gt;Клиентские коннекторы проверяют идентификатор ресурса, чтобы выбрать подходящий механизм связи для каждого запроса. Например, клиент может быть сконфигурирован для соединения с конкретным прокси-компонентом, возможно, таким, который действует как фильтр аннотаций, когда идентификатор указывает, что это локальный ресурс. Аналогично, клиент может быть настроен на отклонение запросов для некоторого подмножества идентификаторов.&lt;/p&gt;

&lt;p&gt;REST не ограничивает связь конкретным протоколом, но ограничивает интерфейс между компонентами и, следовательно, дополняет область предположений о взаимодействии и реализации, которые могли бы быть сделаны между компонентами. Например, основным протоколом передачи в Интернете является HTTP, но в архитектуру также входит беспрепятственный доступ к ресурсам, которые создаются на уже существующих сетевых серверах, включая FTP, Gopher и WAIS. Взаимодействие с этими службами ограничено семантикой коннектора REST. Это ограничение жертвует некоторыми из преимуществ других архитектур, таких как взаимодействие с состоянием протокола обратной связи по релевантности, такого как WAIS, чтобы сохранить преимущества единого универсального интерфейса для семантики коннектора. В свою очередь, общий интерфейс позволяет получить доступ к множеству сервисов через один прокси. Если приложению требуются дополнительные возможности другой архитектуры, оно может реализовать и вызвать эти возможности как отдельную систему, работающую параллельно, подобно тому, как веб-архитектура взаимодействует с ресурсами "telnet" и "mailto".&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3.3 Представление данных
&lt;/h3&gt;

&lt;p&gt;Представление данных раскрывает состояние приложения при прохождении информации через компоненты. Поскольку REST специально нацелен на распределенные информационные системы, он рассматривает приложение как связную структуру информации и альтернатив управления, с помощью которых пользователь может выполнять желаемую задачу. Например, поиск слова в онлайн-словаре - это одно из приложений, как, например, путешествие по виртуальному музею или просмотр набора заметок для подготовки к экзамену. Каждое приложение определяет цели для базовой системы, по которым можно измерить производительность системы.&lt;/p&gt;

&lt;p&gt;Взаимодействие компонентов происходит в форме сообщений динамического размера. Сообщения малого или среднего размера используются для семантики управления, но основная часть работы приложения выполняется через сообщения большого размера, содержащие полное представление ресурса. Наиболее частой формой семантики запроса является извлечение представления ресурса (например, метода «GET» в HTTP), который часто можно кэшировать для последующего повторного использования.&lt;/p&gt;

&lt;p&gt;REST концентрирует все состояние управления в представлениях, полученных в ответ на взаимодействия. Цель состоит в том, чтобы повысить расширяемость сервера, устраняя необходимость для сервера поддерживать осведомленность о состоянии клиента за пределами текущего запроса. Поэтому состояние приложения определяется его ожидающими запросами, топологией подключенных компонентов (некоторые из которых могут быть фильтрацией буферизованных данных), активными запросами на этих коннекторах, потоком данных представлений в ответ на эти запросы и обработкой этих представления, как они получены агентом пользователя.&lt;/p&gt;

&lt;p&gt;Приложение достигает устойчивого состояния, когда у него нет невыполненных запросов; то есть он не имеет ожидающих запросов, и все ответы на его текущий набор запросов были полностью получены или получены до такой степени, что их можно рассматривать как поток данных представления. Для приложения браузера это состояние соответствует «веб-странице», включающей первичное представление и вспомогательные представления, такие как встроенные изображения, встроенные апплеты и таблицы стилей. Важность стационарных состояний приложений проявляется в их влиянии как на воспринимаемую пользователем производительность, так и на интенсивность трафика сетевых запросов.&lt;/p&gt;

&lt;p&gt;Воспринимаемая пользователем производительность приложения браузера определяется задержкой между установившимися состояниями: периодом времени между выбором гипермедиа-ссылки на одной веб-странице и моментом представления полезной информации для следующей веб-страницы. Поэтому оптимизация производительности браузера сосредоточена на уменьшении этой задержки связи.&lt;/p&gt;

&lt;p&gt;Поскольку основанные на REST архитектуры обмениваются данными главным образом посредством передачи представлений ресурсов, на задержку могут влиять как структура протоколов связи, так и конструкция форматов данных представления. Возможность постепенной визуализации данных ответа по мере их получения определяется конструкцией типа носителя и доступностью информации макета (визуальные размеры встроенных объектов) в каждом представлении.&lt;/p&gt;

&lt;p&gt;Важное наблюдение: наиболее эффективный сетевой запрос - это тот, который не использует сеть. Другими словами, возможность многократного использования кэшированного ответа приводит к значительному улучшению производительности приложения. Хотя использование кэша добавляет некоторую задержку к каждому отдельному запросу из-за накладных расходов на поиск, средняя задержка запроса значительно уменьшается, когда даже небольшой процент запросов приводит к используемым обращениям к кешу.&lt;/p&gt;

&lt;p&gt;Следующее состояние управления приложением находится в представлении первого запрошенного ресурса, поэтому получение этого первого представления является приоритетом. Поэтому взаимодействие REST улучшается с помощью протоколов, которые «сначала отвечают, а потом думают». Другими словами, протокол, который требует множественных взаимодействий для каждого действия пользователя, для того, чтобы выполнить такие вещи, как согласование возможностей функции перед отправкой ответа на контент, будет значительно медленнее, чем протокол, который сначала отправляет то, что наиболее вероятно будет оптимальным, а затем обеспечивает список альтернатив, которые клиент может получить, если первый ответ неудовлетворителен.&lt;/p&gt;

&lt;p&gt;Состояние приложения контролируется и сохраняется пользовательским агентом и может состоять из представлений от нескольких серверов. В дополнение к освобождению сервера от проблем расширяемости сохранения состояния, это позволяет пользователю напрямую манипулировать состоянием (например, историей веб-браузера), предвидеть изменения в этом состоянии (например, карты ссылок и предварительную выборку представлений) и выполнять переход из одного приложения в другое (например, закладки и диалоги ввода URI).&lt;/p&gt;

&lt;p&gt;Следовательно, модельное приложение представляет собой механизм, который перемещается из одного состояния в другое путем изучения и выбора среди альтернативных переходов состояний в текущем наборе представлений. Не удивительно, что это точно соответствует пользовательскому интерфейсу гипермедиа браузера. Однако стиль не предполагает, что все приложения являются браузерами. Фактически, детали приложения скрыты от сервера с помощью универсального интерфейса коннектора, и, таким образом, пользовательский агент может в равной степени быть автоматическим роботом, выполняющим поиск информации для службы индексирования, личным агентом, который ищет данные, которые соответствуют определенным критериям, или обслуживанием. Паук занят патрулированием информации на предмет неработающих ссылок или измененного контента.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>разбираемся с байткодом v8 [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:36 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/v8-1ioe</link>
      <guid>https://dev.to/scumdograscalhouse/v8-1ioe</guid>
      <description>&lt;p&gt;Вольный перевод статьи Franziska Hinkelmann "&lt;a href="https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775"&gt;Understanding V8’s Bytecode&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Разбираемся с байткодом V8
&lt;/h1&gt;

&lt;p&gt;V8 - это опенсорсный движок для JavaScript, разработанный Google. Chrome, Node.js и многие другие приложения используют V8. В этой статье будет предложено объяснение формата байткода V8, который, на самом деле, станет очень простым для чтения, как только вы поймете некоторые основные концепты.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZBhOviFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/450/1%2Ag8Tutq52nx6x44ELgz_UWg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZBhOviFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/450/1%2Ag8Tutq52nx6x44ELgz_UWg.png" alt="https://miro.medium.com/max/450/1*g8Tutq52nx6x44ELgz_UWg.png" width="450" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ignition, lift-off! Интерпретатор Ignition является частью пайплайна компилятора с 2016-го года.&lt;/p&gt;

&lt;p&gt;Когда V8 компилирует JavaScript код, парсер генерирует абстрактное синтаксическое дерево. Синтаксическое дерево - это древовидное представление синтаксической структуры JavaScript кода, из которого интерпретатор Ignition генерирует байткод. TurboFan - это оптимизирующий компилятор, который берет байткод и генерирует из него оптимизированный машинный код.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b6zQ4wos--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1019/1%2AZIH_wjqDfZn6NRKsDi9mvA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b6zQ4wos--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1019/1%2AZIH_wjqDfZn6NRKsDi9mvA.png" alt="https://miro.medium.com/max/1019/1*ZIH_wjqDfZn6NRKsDi9mvA.png" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Пайплайн компилятора V8&lt;/p&gt;

&lt;p&gt;Если хотите узнать, для чего нам нужно именно два режима выполнения, можете посмотреть мое видео с конференции JSConfEU:&lt;/p&gt;

&lt;p&gt;video: &lt;a href="https://www.youtube.com/watch?v=p-iiEDtpy6I&amp;amp;feature=emb_logo"&gt;https://www.youtube.com/watch?v=p-iiEDtpy6I&amp;amp;feature=emb_logo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bytecode является абстракцией машинного кода&lt;/strong&gt;. Компилировать байткод в машинный код будет проще, если байткод был спроектирован с одинаковой вычислительной моделью, что и физический CPU. Поэтому интерпретаторы обычно являются регистровыми или стэковыми машинами. &lt;strong&gt;Ignition - регистровая машина с аккумулятором (регистром процессора).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sDNjj4PO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/995/1%2Aaal_1sevnb-4UaX8AvUQCg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sDNjj4PO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/995/1%2Aaal_1sevnb-4UaX8AvUQCg.png" alt="https://miro.medium.com/max/995/1*aal_1sevnb-4UaX8AvUQCg.png" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Байткоды V8 можно рассматривать как маленькие строительные блоки,&lt;/strong&gt; которые выстраивают любую JavaScript функциональность, когда складываются вместе. V8 имеет несколько сотен байткодов. Есть байткоды для операций, вроде &lt;code&gt;Add&lt;/code&gt; и &lt;code&gt;TypeOf&lt;/code&gt;, или для загрузки свойств, вроде &lt;code&gt;LdaNamedProperty&lt;/code&gt;. V8 также имеет несколько специфичных байткодов, вроде &lt;code&gt;CreateObjectLiteral&lt;/code&gt; и &lt;code&gt;SuspendGenerator&lt;/code&gt;. Головной файл &lt;a href="https://github.com/v8/v8/blob/master/src/interpreter/bytecodes.h"&gt;bytecodes.h&lt;/a&gt; определяет полный их список.&lt;/p&gt;

&lt;p&gt;Каждый bytecode определяет входные и выходные данные как операнды регистра. Ignition использует геристры &lt;code&gt;r0, r1, r2, ...&lt;/code&gt; и аккумулятор, который также используют почти все байткоды. Он похож на обычный регистр, но байткоды его не определяют его точно. Например, &lt;code&gt;Add r1&lt;/code&gt; добавляет значение регистра &lt;code&gt;r1&lt;/code&gt; к значению в аккумуляторе, что позволяет сохранять память и хранить байткоды в более коротком виде.&lt;/p&gt;

&lt;p&gt;Множество байткодов начинается с &lt;code&gt;Lda&lt;/code&gt; или &lt;code&gt;Sta&lt;/code&gt;. &lt;strong&gt;&lt;code&gt;a&lt;/code&gt;&lt;/strong&gt; в выражении &lt;code&gt;Ld**a**&lt;/code&gt; и &lt;code&gt;St**a**&lt;/code&gt; обозначает &lt;strong&gt;a&lt;/strong&gt;ккумулятор. Например, &lt;code&gt;LdaSmi [42]&lt;/code&gt; подгружает маленькое целое число (Small Integer - Smi) &lt;code&gt;42&lt;/code&gt; в регистр аккумулятора. &lt;code&gt;Star r0&lt;/code&gt; будет хранить текущее значение регистра &lt;code&gt;r0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Пора взглянуть на байткод реальной функции.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function incrementX(obj) {
  return 1 + obj.x;
}

incrementX({x: 42});  // Компилятор V8 ленив. Если вы не запустите функцию - он ее не интерпретирует.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Если вы хотите посмотреть на байткод V8 в JavaScript коде, то исполните команду с флагом --print-bytecode (для D8 и Node.js версии 8.3 и выше). Для Chrome вам нужно запустить его из командной строки с флагом --js-flags="--print-bytecode", см. &lt;a href="https://www.chromium.org/developers/how-tos/run-chromium-with-flags"&gt;Запуск Chromium с флагами&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node --print-bytecode incrementX.js
...
[generating bytecode for function: incrementX]
Parameter count 2
Frame size 8
  12 E&amp;gt; 0x2ddf8802cf6e @    StackCheck
  19 S&amp;gt; 0x2ddf8802cf6f @    LdaSmi [1]
        0x2ddf8802cf71 @    Star r0
  34 E&amp;gt; 0x2ddf8802cf73 @    LdaNamedProperty a0, [0], [4]
  28 E&amp;gt; 0x2ddf8802cf77 @    Add r0, [6]
  36 S&amp;gt; 0x2ddf8802cf7a @    Return
Constant pool (size = 1)
0x2ddf8802cf21: [FixedArray] in OldSpace
 - map = 0x2ddfb2d02309 &amp;lt;Map(HOLEY_ELEMENTS)&amp;gt;
 - length: 1
           0: 0x2ddf8db91611 &amp;lt;String[1]: x&amp;gt;
Handler Table (size = 16)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Большую часть вывода можно игнорировать и фокусироваться только на самих байткодах. Ниже описано шаг за шагом, что значит каждый байткод.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;LdaSmi [1]&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;LdaSmi [1]&lt;/code&gt; загружает постоянное значение &lt;code&gt;1&lt;/code&gt; в аккумулятор.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p-wYkXBU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/311/1%2AWIECS2Gd701BnheqXrWbag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p-wYkXBU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/311/1%2AWIECS2Gd701BnheqXrWbag.png" alt="https://miro.medium.com/max/311/1*WIECS2Gd701BnheqXrWbag.png" width="311" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Star r0&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Далее, &lt;code&gt;Star r0&lt;/code&gt;  в регистре &lt;code&gt;r0&lt;/code&gt; хранит значение, которое на момент находится в аккумуляторе (&lt;code&gt;1&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P6JKAo_J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/311/1%2A271aYN7VC6ltaleyDfwhXg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P6JKAo_J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/311/1%2A271aYN7VC6ltaleyDfwhXg.png" alt="https://miro.medium.com/max/311/1*271aYN7VC6ltaleyDfwhXg.png" width="311" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;&lt;code&gt;LdaNamedProperty a0, [0], [4]&lt;/code&gt;&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;LdaNamedProperty&lt;/code&gt; загружает именованное свойство &lt;code&gt;a0&lt;/code&gt; в аккумулятор. &lt;code&gt;ai&lt;/code&gt; ссылается на i-ый аргумент &lt;code&gt;incrementX()&lt;/code&gt;. В этом примере мы ищем именованное свойство в &lt;code&gt;a0&lt;/code&gt;, первый аргумент &lt;code&gt;incrementX()&lt;/code&gt;. Имя определяется константой  &lt;code&gt;0&lt;/code&gt;. &lt;code&gt;LdaNamedProperty&lt;/code&gt; использует &lt;code&gt;0&lt;/code&gt; для нахождения имени в отдельной таблице:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- length: 1
           0: 0x2ddf8db91611 &amp;lt;String[1]: x&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, &lt;code&gt;0&lt;/code&gt; отображает значение &lt;code&gt;x&lt;/code&gt;, потому этот байткод загружает &lt;code&gt;obj.x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Для чего используется операнд со значением &lt;code&gt;4&lt;/code&gt;? Это индекс так называемого &lt;em&gt;вектора обратной связи&lt;/em&gt; функции &lt;code&gt;incrementX()&lt;/code&gt;, которые содержит нужную для оптимизаций производительности рантайм информацию.&lt;/p&gt;

&lt;p&gt;Теперь регистры выглядят так:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dZhqw6ys--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/311/1%2AsGFN376VKgf2hWXctBqZnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dZhqw6ys--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/311/1%2AsGFN376VKgf2hWXctBqZnw.png" alt="https://miro.medium.com/max/311/1*sGFN376VKgf2hWXctBqZnw.png" width="311" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Add r0, [6]&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Последняя инструкция добавляет &lt;code&gt;r0&lt;/code&gt; в аккумулятор, возвращая в результате &lt;code&gt;43&lt;/code&gt;. &lt;code&gt;6&lt;/code&gt; - это еще один индекс вектора обратной связи.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JBWf1dCZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/311/1%2ALAHuYIvZaXX8jH_STNHfmQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JBWf1dCZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/311/1%2ALAHuYIvZaXX8jH_STNHfmQ.png" alt="https://miro.medium.com/max/311/1*LAHuYIvZaXX8jH_STNHfmQ.png" width="311" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Return&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Return&lt;/code&gt; возвращает значение аккумулятора. Это конец функции &lt;code&gt;incrementX()&lt;/code&gt;. Вызывающий функцию  &lt;code&gt;incrementX()&lt;/code&gt; операнд получает значение &lt;code&gt;43&lt;/code&gt; из аккумулятора и может начать работать с ним.&lt;/p&gt;

&lt;p&gt;На первый взгляд, байткод V8 может показаться какой-то загадкой, особенно со всей остальной информацией в выводе. Но как только вы поймете что Ignition это регистровая машина с аккумулятором регистра, вы сможете понять за что отвечает большинство байткодов.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3GGUVobd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1024/1%2AZrJKJqBsksWd-8uKM9OvgA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3GGUVobd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/max/1024/1%2AZrJKJqBsksWd-8uKM9OvgA.png" alt="https://miro.medium.com/max/1024/1*ZrJKJqBsksWd-8uKM9OvgA.png" width="800" height="3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Примечание: в статье описывается байткод V8 версии 6.2, Chrome версии 62 и Node версии 9. Мы постоянно работаем над улучшением потребления памяти и производительности V8, потому детали в других версиях могут отличаться.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>как начать just-in-time-ить [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:33 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/just-in-time-1nm0</link>
      <guid>https://dev.to/scumdograscalhouse/just-in-time-1nm0</guid>
      <description>&lt;p&gt;Вольный перевод статьи Fedor Indutny "&lt;a href="https://darksi.de/4.how-to-start-jitting/"&gt;How to start JIT-ting&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Как начать Just-In-Time-ить
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Предпосылки
&lt;/h2&gt;

&lt;p&gt;Большинство разработчиков что-то слышало об JIT компиляторах и как они могут замедлять интерпретируемые языки в сравнении с нативным кодом. Однако, не так много их них понимает как именно эта JIT штука работает, и еще меньше из них писали свои собственные компиляторы.&lt;/p&gt;

&lt;p&gt;Как мне кажется, хотя бы базовые знания о внутренностях компилятора могут мощно прокачать понимание запускаемого обеспечением кода.&lt;/p&gt;

&lt;p&gt;В этой статье мы посетим несколько вершин на JIT-острове, и возможно даже реализуем компилятор сами!&lt;/p&gt;

&lt;h2&gt;
  
  
  С чего начнем
&lt;/h2&gt;

&lt;p&gt;Зная основы компилятора, мы можем вв что каждый компилятор трансформирует входные данные какого-то формата (обычно, исходный код) в выходные такого же или другого формата (обычно, в машинный код). JIT комплияторы не исключение.&lt;/p&gt;

&lt;p&gt;Что делает их исключительными, так это то, что они запускаются не раньше времени (как gcc, clang и другие), а Just-In-Time (&lt;em&gt;вовремя&lt;/em&gt;) (т.е. прямо перед выполнением выходных данных комплиятора).&lt;/p&gt;

&lt;p&gt;чтобы начать разрабатывать наш собственный JIT компилятор, понадобится выбрать язык для входных данных. Принимая во внимание &lt;a href="http://adambard.com/blog/top-github-languages-for-2013-so-far/"&gt;ТОП ЯЗЫКОВ 2013 ГОДА НА GITHUB&lt;/a&gt;, JavaScript выглядит подходяще для реализации некоторого ограниченного подмножества с упрощенной семантикой. Более того, мы реализуем JIT компилятор на самом JavaScript. Это можно назвать МЕТА-МЕТА!&lt;/p&gt;

&lt;h2&gt;
  
  
  Абстрактное Синтаксическое Дерево (&lt;strong&gt;AST)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;На вход наш компилятор будет принимать JavaScript и возвращать (и немедленно выполнять) машинный код для очень популярной X64 платформы. Но, посокльку люддям удобнее работать с текстовым представлением, разработчики компиляторов обычно стремятся создать несколько Промежуточных Представлений (Intermediate Representations) перед генерацией окончательного машинного кода.&lt;/p&gt;

&lt;p&gt;Поскольку мы пишем упрощенную версию, одного промежуточного представления будет достаточно, и для этого я выбрал представление в виде Абстрактное Синтаксическое Дерево (Abstract Syntax Tree).&lt;/p&gt;

&lt;p&gt;Получить  AST из JavaScript кода достаточно просто и мы можем выбрать несколько (из десятков) библиотек вроде &lt;a href="https://github.com/ariya/esprima"&gt;esprima&lt;/a&gt;, &lt;a href="https://github.com/ariya/esprima"&gt;uglify-js&lt;/a&gt; и т.д. . Чтобы не отходит далеко от туториала, я порекомендую вам выбрать &lt;a href="https://github.com/ariya/esprima"&gt;esprima&lt;/a&gt;. Он имеет неплохой и четко определенный &lt;a href="https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API"&gt;формат выходных данных&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Например, такой код &lt;code&gt;obj.method(42)&lt;/code&gt; создаст (с помощью &lt;code&gt;esprima.parse("...")&lt;/code&gt;) дерево следующего вида:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ type: 'Program',
  body:
   [ { type: 'ExpressionStatement',
       expression:
        { type: 'CallExpression',
          callee:
           { type: 'MemberExpression',
             computed: false,
             object: { type: 'Identifier', name: 'obj' },
             property: { type: 'Identifier', name: 'method' } },
          arguments: [ { type: 'Literal', value: 42 } ] } } ] }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Машинный код
&lt;/h2&gt;

&lt;p&gt;Давайте обобщим: у нас есть исходный код (&lt;em&gt;check&lt;/em&gt;), его Абстрактное Синтаксическое Дерево (&lt;em&gt;check&lt;/em&gt;), осталось только получить машинный код.&lt;/p&gt;

&lt;p&gt;Если вы уже знакомы с assembly то можете пропустить эту главу поскольку она содержит только базовое введение в тему. ОДнако, если нет, последующая глава, без этих основ,  может показаться вам сложной для понимания. Потому оставайтесь здесь, много времени не займет!&lt;/p&gt;

&lt;p&gt;Язык Assembly - это ближайшее текстовое представление бинарного кода который понимает и(или) может запустить ваш CPU. Учитывая, что процессоры выполнябт код читая и запускаю иснтрукции по одной, может показаться логичным, что одна строка asse,bly программы представляет собой одну инструкцию:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov rax, 1    ; Положить 1 в регистр `rax`
mov rbx, 2    ; Положить 2 в регистр `rbx`
add rax, rbx  ; Вычислить сумму `rax` и `rbx` и положить ее в регистр `rax`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Выходными данными этой прграммы будет являться 3 (при условии, что вы получите их из  &lt;code&gt;rax&lt;/code&gt; регистра). И, как вы уже наверно догадались, программа будет ложить данные в CPU слоты (&lt;a href="http://en.wikipedia.org/wiki/Processor_register"&gt;регистры&lt;/a&gt;) и просить CPU вычислить их сумму.&lt;/p&gt;

&lt;p&gt;Обычно процессоры имеют достаточное количество регистров для хранения результата промежуточных операций, но в некоторых случаях вам понадобится хранить/загружать данные из памяти кмопьютера:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mov rax, 1
mov [rbp-8], rbx  ; Сохранить rbx регистр в слот стека
mov rbx, 2
add rax, rbx
mov rbx, [rbp-8]  ; Восстановить rbx регистр из слота стека
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Регистры имеют имена, слоты памяти имеют адреса. Эти ардеа обычно используют &lt;code&gt;[...]&lt;/code&gt; синтаксис. Например, &lt;code&gt;[rbp-8]&lt;/code&gt; означает: возьми значение регистра &lt;code&gt;rbp&lt;/code&gt; , вычти из него &lt;code&gt;8&lt;/code&gt;, и достучись до слота памяти, используя значение результата в качестве адреса.&lt;/p&gt;

&lt;p&gt;Как вы можете видеть мы используем &lt;code&gt;rbp&lt;/code&gt; регистр. &lt;code&gt;rbp&lt;/code&gt; обычно содержит адреса по которым переменные из стека (т.е. переменные, которые хранятся в текущем процедурной &lt;a href="http://en.wikipedia.org/wiki/Stack_(abstract_data_type)"&gt;стеке&lt;/a&gt;) запускаются; &lt;code&gt;8&lt;/code&gt; это размер  &lt;code&gt;rbx&lt;/code&gt; регистра (и всех остальынх регистров, название которые начинается на &lt;code&gt;r&lt;/code&gt;), и поскольку &lt;a href="http://en.wikipedia.org/wiki/Stack_(abstract_data_type)"&gt;стек&lt;/a&gt; растет снизу вверх, нам нужно вычесть его из  &lt;code&gt;rbp&lt;/code&gt; чтобы получить адрес свободного слота.&lt;/p&gt;

&lt;p&gt;Есть много нюансов в программировании на низком уровне и к сожалению я не собираюсь обо всех них здесь рассказывать. Также имейте ввиду что я предоставляю очень поверхностное описание и то что происходит здесь на самом деле  может быть гораздо более сложным.&lt;/p&gt;

&lt;p&gt;Понятий поясненных выше должны быть достаточно чтобы приступить к генерации кода.&lt;/p&gt;

&lt;h2&gt;
  
  
  Генерация кода
&lt;/h2&gt;

&lt;p&gt;Реализовать полностью JavaScript будет довольно сложно, потому сейчас мы реализуем только упрощенную версию арифметического движка.&lt;/p&gt;

&lt;p&gt;Лучшим и простейшим способом сделать это будет с помощью прохода по AST с помощью &lt;a href="http://en.wikipedia.org/wiki/Depth-first_search"&gt;поиска в глубину&lt;/a&gt;, генерирую машинный код для каждого узла. Вы можете подумать как можно генерирвоать машинный код для такого memory-safe языка как JavaScript. Здесь я представлю вам &lt;a href="https://github.com/indutny/jit.js"&gt;jit.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Это node.js модуль (и C++ аддон, по сути) способный генерировать и выполнять машинны код используя assembly-подобный JavaScript синтаксис:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var jit = require('jit.js');

var fn = jit.compile(function() {
  this.Proc(function() {
    this.mov('rax', 42);
    this.Return();
  });
});
console.log(fn());  // 42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Давайте уже напишем это
&lt;/h2&gt;

&lt;p&gt;Таким образом нам осталось сделать только одну вещь - модуль для прохода по AST, сгенерированному с помозью &lt;a href="https://github.com/ariya/esprima"&gt;esprima&lt;/a&gt;. К счастью, учитывая его структуру и наш минималистичный дизайн компилятора это должно быть довольно просто.&lt;/p&gt;

&lt;p&gt;Мы внедрим поддержку:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Числовых литералов (&lt;code&gt;{ type: 'Literal', value: 123 }&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Бинарных выражений с операторами: &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt; (&lt;code&gt;{ type: 'BinaryExpression', operator: '+', left: ... , right: .... }&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Унарных выражений с оператором &lt;code&gt;-&lt;/code&gt; (&lt;code&gt;{ type: 'UnaryExpression', operator: '-', argument: ... }&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Все эти операции выполняются на целых числах, потому не ожидайте что они сработают со значениями вроде &lt;code&gt;0.5&lt;/code&gt;, &lt;code&gt;0.66666&lt;/code&gt; и т.д. .&lt;/p&gt;

&lt;p&gt;Во время обработки выражения мы будем посещать каждый поддерживаемый узел AST и генерировать код, который вернет результат в &lt;code&gt;rax&lt;/code&gt; регистр. Звучить легко, да? Здесь есть одно правило: мы должны сохранять все остальные регистры чистыми после выхода из узла дерева. Другими словами, это значит что мы должны сохранять все использованные регистры и восстанавливать их состояние после того как они не нужны. К счастью, CPU имеет две волшебные операции&lt;code&gt;push&lt;/code&gt; и &lt;code&gt;pop&lt;/code&gt; которые помогут нам с этой задачей.&lt;/p&gt;

&lt;p&gt;Таким получится код (с комментариями):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var jit = require('jit.js'),
    esprima = require('esprima'),
    assert = require('assert');

var ast = esprima.parse(process.argv[2]);

// Compile
var fn = jit.compile(function() {
  // Создаем шаблон для входных данных по умолчанию
  this.Proc(function() {
    visit.call(this, ast);

    // Результат должен быть в 'rax'

    // Создаем шаблон для выходных данных
    this.Return();
  });
});

// Выполнение
console.log(fn());

function visit(ast) {
  if (ast.type === 'Program')
    visitProgram.call(this, ast);
  else if (ast.type === 'Literal')
    visitLiteral.call(this, ast);
  else if (ast.type === 'UnaryExpression')
    visitUnary.call(this, ast);
  else if (ast.type === 'BinaryExpression')
    visitBinary.call(this, ast);
  else
    throw new Error('Неизвестный AST узел: ' + ast.type);
}

function visitProgram(ast) {
  assert.equal(ast.body.length,
               1,
               'Поддерживается только один оператор');
  assert.equal(ast.body[0].type, 'ExpressionStatement');
  visit.call(this, ast.body[0].expression);
}

function visitLiteral(ast) {
  assert.equal(typeof ast.value, 'number');
  assert.equal(ast.value | 0,
               ast.value,
               'Поддерживаются только целочисленные значения');

  this.mov('rax', ast.value);
}

function visitBinary(ast) {
  // Сохраняем 'rbx' после того, как вышли из AST узла
  this.push('rbx');

  // Посещаем правую стороно выражения
  visit.call(this, ast.right);

  // Перемещаем в 'rbx'
  this.mov('rbx', 'rax');

  // Посещаем левую сторну выражения (результат в 'rax')
  visit.call(this, ast.left);

  //
  // Обобщяя, левая сторона хранится в 'rax', правая - в 'rbx'
  //

  // Выполняем бинарную операцию
  if (ast.operator === '+') {
    this.add('rax', 'rbx');
  } else if (ast.operator === '-') {
    this.sub('rax', 'rbx');
  } else if (ast.operator === '*') {
    // Умножение со знаком
    // rax = rax * rbx
    this.imul('rbx');
  } else if (ast.operator === '/') {
    // Сохраняем 'rdx'
    this.push('rdx');

    // idiv выполняет деление rdx:rax на rbx, следовательно, нам нужно очистить rdx
    // перед запуском
    this.xor('rdx', 'rdx');

    // Деление со знаком, rax = rax / rbx
    this.idiv('rbx');

    // Восстанавливаем 'rdx'
    this.pop('rdx');
  } else if (ast.operator === '%') {
    // Сохраняем 'rdx'
    this.push('rdx');

    // Готовимся выполнить idiv
    this.xor('rdx', 'rdx');
    this.idiv('rbx');

    // idiv помещает остаток в 'rdx'
    this.mov('rax', 'rdx');

    // Восстанавливаем 'rdx'
    this.pop('rdx');
  } else {
    throw new Error('Неподдерживаемый бинарный оператор: ' + ast.operator);
  }

  // Восстанавливаем 'rbx'
  this.pop('rbx');

  // Результат в 'rax'
}

function visitUnary(ast) {
  // Посещаем аргумент и складываем результат в 'rax'
  visit.call(this, ast.argument);

  if (ast.operator === '-') {
    // Делаем аргумент отрицательным
    this.neg('rax');
  } else {
    throw new Error('Неподдерживаемый унарный оператор: ' + ast.operator);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Вы можете попробоать сами склонировав исходный код с &lt;a href="https://github.com/indutny/jit.js/tree/master/example/basic"&gt;github&lt;/a&gt;, запустив &lt;code&gt;npm install&lt;/code&gt; и вуаля!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node ./main.js '1 + 2 * 3'
7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>назначаем числа [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:31 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/-5fpe</link>
      <guid>https://dev.to/scumdograscalhouse/-5fpe</guid>
      <description>&lt;p&gt;Вольный перевод статьи Fedor Indutny "&lt;a href="https://darksi.de/5.allocating-numbers/"&gt;Allocating numbers&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Назначаем числа
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Цели
&lt;/h2&gt;

&lt;p&gt;В прошлой статье мы создали JIT компилятор, поддерживающий очень ограниченное количесвто подномежств JavaScript: целые числа, бинарные математические операции операторы (&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;), и унарный оператор &lt;code&gt;-&lt;/code&gt;. В этот раз мы расширим его добавлением поддержки чисел с плавающей точкой, а, чтобы процесс стал живее и веселее, мы будем выделять и хранить эти числа в куче.&lt;/p&gt;

&lt;p&gt;Поскольку мы внедряем функциональность небольшими шагами, на этом этапе наша куча не будет иметь сборщика мусора и будет жить внутри чанка памяти фиксированного размера (скажи "ура" простоте!).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Stubs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Отталкиваясь от наших целей мы можем установить внутренние структуры для желаемых фич. По сути, нам понадобится процедура выделения памяти, которая генерирует и возвращает подходящий нашим целям участок памяти.&lt;/p&gt;

&lt;p&gt;Такое выделение может быть сгенерировано для каждого узла AST с помощью серий встроенных assembly инструкций, которые великолепно работают и, что более важно, невероятно быстро исполняются для коротких операций. Но из-за относительного большого размера кодовой базы это процедуры, финальный вывод в виде машинного кода может не поместиться в кэше CPU, вызывая потенциальные проблемы с производительностью всей системы.&lt;/p&gt;

&lt;p&gt;Как правило, это считается плохой практикой. Более лучшим подходом будет будет параметризация таких блоков кода в общие процедуры, называемые &lt;code&gt;stubs&lt;/code&gt; (я взял это название из &lt;a href="https://github.com/v8/v8/blob/master/src/ia32/code-stubs-ia32.cc"&gt;кода v8&lt;/a&gt; и, похоже, они имеют такое же название и в других VM). Для еще большей оптимизации эти процедуры можно компилировать лениво, т.е. мы не должны компилировать участки, которые не используются генерируемым кодом. Эта техника хорошо сказывается на времени компиляции и размере выполняемого кода (и, соответснвенно, кэше CPU).&lt;/p&gt;

&lt;p&gt;К счастью, &lt;a href="https://github.com/indutny/jit.js"&gt;jit.js&lt;/a&gt; позволяет достаточно легко генерировать &lt;em&gt;stubs&lt;/em&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var stubs = jit.stubs();

stubs.define('Allocate', function() {
  // Наш код здесь
  // ....

  // Возвращаемся к месту вызова
  this.Return();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Просто, не так ли? Чтобы использовать это в нашем JIT компиляторе нам нужно передать его в аргументы:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jit.compile(function() {
  // Пояснение:
  // Прочитать адрес стаба 'Allocate' в регистре 'rax' и
  // вызвать его.
  this.stub('rax', 'Allocate');

  this.Return();
}, { stubs: stubs });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Как я заметил выше, сгенерированы и переиспользованы будут только те стабы, которые исподьзовались во вермя процесса компиляции.&lt;/p&gt;

&lt;h2&gt;
  
  
  Куча
&lt;/h2&gt;

&lt;p&gt;Теперь мы можем перейти к выделению памяти. Но сначала давайте взнглянем на структуру и организацию кучи.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Куча&lt;/em&gt; - это место, где JavaScript (и многие другие) VM создает и хранит обхекты (обычно те которые не могут уместиться в регистрах CPU). Некоторые объекты кучи могут содержать ссылки на другие объекты (другими словами, могут ссылаться на них). Все живущие объекты и их ссылки создают ориентированный граф начиная с так называех &lt;em&gt;корней&lt;/em&gt; (который обычно являются глобальными переменными и указателями в стеке).&lt;/p&gt;

&lt;p&gt;И хоть она обычно используется для VM, сборка мусора не обязательна для кучи. Многие виртуальные машины и языки выбирают неуправляемую память вместо того (C/C++ как пример). В таких случаях (как пользователь языка) будет необзодимо явно очищать неиспользуемые ресурсы чтобы не исчерпать память.&lt;/p&gt;

&lt;p&gt;Но по очевидным причинам компилятор подмножеств JavaScript который мы реализуем должен поддерживать как управляемую память так и сборку мусора (что сделаем позже).&lt;/p&gt;

&lt;p&gt;Есть тонны кнги котоыре могут дать вам продвинутое представление о распределении кучи и сборке мусора (я порекомендую &lt;a href="http://www.amazon.com/The-Garbage-Collection-Handbook-Management/dp/1420082795/ref=sr_1_1?ie=UTF8&amp;amp;qid=1383600127&amp;amp;sr=8-1&amp;amp;keywords=garbage+collection+handbook"&gt;The Garbage Collection Handbook&lt;/a&gt;) и и значительно много способов выделения и сборки памяти в куче.&lt;/p&gt;

&lt;p&gt;Обычно вам понадобится выбрать между скоростью выделения и фрагментацией памяти. Но поскольку мы не углубляемся в тему я порекомендую остановиться на методе "bump allocation".&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Bump allocation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;bump allocation для фиксированных страниц работает следующим образом.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Взять чанк памяти фиксированного значения (страница)&lt;/li&gt;
&lt;li&gt;Отдать последующие фрагменты в виде возвращаемого значение процедуры выделения.&lt;/li&gt;
&lt;li&gt;Сжимая или перемещая живущие объекты в новый чанк памяти, запускать сборку мусора и очищать неиспользуемое пространство,когда кончается память, (заменяя ссылки на живущие обхекты).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Используя &lt;a href="https://github.com/indutny/jit.js"&gt;jit.js&lt;/a&gt; и стабы, эта процедура может выглядеть следующим образом:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Создаем чанки памяти фиксированного размера
var page = new Buffer(1024);

// Устанавливаем указатели на начало и конец страницы
var offset = jit.ptr(page);
var end = jit.ptr(page, page.length);

stubs.define('Alloc', function() {

  // Сохраняем регистры 'rbx' и 'rcx'
  this.spill(['rbx', 'rcx'], function() {
    // Загружаем `offset`
    //
    // ПРИМЕЧАНИЕ: Мы будем использовать указатель для переменной `offset`, чтобы была вохможность
    // позже обновить ее
    this.mov('rax', this.ptr(offset));
    this.mov('rax', ['rax']);

    // Конец заргрузки
    //
    // ПРИМЕЧАНИЕ: То же самое относится и к концу страницы, но мы не обновляем его прямо сейчас
    this.mov('rbx', this.ptr(end));
    this.mov('rbx', ['rbx']);

    // Вычисляем новый `offset`
    this.mov('rcx', 'rax');

    this.add('rcx', 16);

    // Проверяем, не переполняется ли буффер фиксированного размера
    this.cmp('rcx', 'rbx');

    // this.j() выполняет условный переход к указанной метке.
    // 'g' означает 'greater'
    // 'overflow' - это имя метки, связанной ниже
    this.j('g', 'overflow');

    // Окей, может двигаться дальше и обновить offset
    this.mov('rbx', this.ptr(offset));
    this.mov(['rbx'], 'rcx');

    // Первый 64-х битный указатель зарезервирован для 'tag',
    // второй имеет значенеи `double`
    this.mov(['rax'], 1);

    // Возвращаем 'rax'
    this.Return();

    // Переполнение :(
    this.bind('overflow')

    // Вызываем javascript функцию!
    // ПРИМЕЧАНИЕ: Штука ниже очень прикольная, но я поясню ее позже
    this.runtime(function() {
      console.log('GC is needed, but not implemented');
    });

    // Краш
    this.int3();

    this.Return();
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Это все! Не совсем просто, но не так уж и сложно!&lt;/p&gt;

&lt;p&gt;Эта процедура будет выдавать последующие куски страницы и даже помечать их (про это поговорим в одном из будующих постов. По сути, они используются определения разных типов объектов кучи. )! &lt;/p&gt;

&lt;p&gt;Стоит отметить пару моментов:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;jit.ptr(buf, offset)&lt;/code&gt; возвращает &lt;code&gt;Buffer&lt;/code&gt;, содержащий указатель на данный  &lt;code&gt;buf&lt;/code&gt; с добавленным к нему &lt;code&gt;offset&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;this.spill()&lt;/code&gt; - это процедура для сохранения и восстановления регистров памяти (обычно называется &lt;em&gt;spilling&lt;/em&gt;). Принимает список регистров и коллбэк. Эти регистры будут сохранены перед входом в коллбэк и восстановлены после выхода из него. ПРИМЕЧАНИЕ: ВОсстановленный код будет сгенерирован перед каждым &lt;code&gt;this.Return()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;this.mov(['rbx'], 'rcx')&lt;/code&gt; сохраняет регистр &lt;code&gt;rcx&lt;/code&gt; в ячейку памяти, на которую указывает значение регистра &lt;code&gt;rbx&lt;/code&gt;. ПРИМЕЧАНИЕ: offset также можно указать здесь: &lt;code&gt;this.mov(['rbx', 8], 'rcx')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;jit.js поддерживает ветвления примитивов: &lt;code&gt;this.cmp(a, b)&lt;/code&gt;, &lt;code&gt;this.j(condition, labelName)&lt;/code&gt;, &lt;code&gt;this.j(labelName)&lt;/code&gt;, &lt;code&gt;this.bind(labelName)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Плавающая запятая
&lt;/h1&gt;

&lt;p&gt;Теперь, когда у нас есть &lt;em&gt;предположительно&lt;/em&gt; работающая процедура, давайте вспомним что должно храниться внутри чанков нашей кучи. Мы создали чанки со значением тега 8 байт и содержимым 8 байт. Этого достаточно чтобы хранить &lt;code&gt;double&lt;/code&gt; (как C тип) числа с плавающей запятой.&lt;/p&gt;

&lt;p&gt;Есть множество assembly инструкций для загрузки/хранения и работы с такими числами. Но заметьте что для работы с ними их нужно хранить в разных наборах регистров: &lt;code&gt;xmm0&lt;/code&gt;, &lt;code&gt;xmm1&lt;/code&gt;, ... &lt;code&gt;xmm15&lt;/code&gt;. Хотя, 64-х битные числа с плавающей запятой могут храниться и в регистрах общего назначения: &lt;code&gt;rax&lt;/code&gt;, &lt;code&gt;rbx&lt;/code&gt;, ... Математические операции возможны только с набором регистров &lt;code&gt;xmm&lt;/code&gt;. Ниже несколько инструкций которые присутстввуют в &lt;code&gt;jit.js&lt;/code&gt; и могут быть полезными нашему компилятору:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;movq('xmm', 'gp')&lt;/code&gt; или &lt;code&gt;movq('gp', 'xmm')&lt;/code&gt; для перемещения 64-х битных значений из регистра общего назначения в xmm, или наоборот.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;movsd('xmm', 'xmm')&lt;/code&gt; для перемещения значения из одного xmm к другому.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;addsd&lt;/code&gt;, &lt;code&gt;mulsd&lt;/code&gt;, &lt;code&gt;subsd&lt;/code&gt;, &lt;code&gt;divsd&lt;/code&gt; - сложение, умножение, вычитание, деление.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cvtsi2sd('xmm', 'gp')&lt;/code&gt;, &lt;code&gt;cvts2si('gp', 'xmm')&lt;/code&gt; для концертации целых чисел в double, и наоборот.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;roundsd('mode', 'xmm', 'xmm')&lt;/code&gt; для округления значения регистра &lt;code&gt;src&lt;/code&gt; с помощью указанного режима &lt;code&gt;mode&lt;/code&gt; (одного из: &lt;code&gt;nearest&lt;/code&gt;, &lt;code&gt;down&lt;/code&gt;, &lt;code&gt;up&lt;/code&gt;, &lt;code&gt;zero&lt;/code&gt;) и помещения результата в регистр &lt;code&gt;dst&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Используя это священное знание, мы можем исправить наш код, чтобы он работал с числами с плавающей запятой (да, мы пока удалим целочисленную поддержку).:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Compile
var fn = jit.compile(function() {
  // Создаем шаблон для входных данных по умолчанию
  this.Proc(function() {
    visit.call(this, ast);

    // Результат должен быть в 'rax'
    //
    // Создаем шаблон для выходных данных
    this.Return();
  });
}, { stubs: stubs });

// Выполнение
console.log(fn());

function visit(ast) {
  if (ast.type === 'Program')
    visitProgram.call(this, ast);
  else if (ast.type === 'Literal')
    visitLiteral.call(this, ast);
  else if (ast.type === 'UnaryExpression')
    visitUnary.call(this, ast);
  else if (ast.type === 'BinaryExpression')
    visitBinary.call(this, ast);
  else
    throw new Error('Неизвестный AST узел: ' + ast.type);
}

function visitProgram(ast) {
  assert.equal(ast.body.length,
               1,
               'Поддерживается только один оператор');
  assert.equal(ast.body[0].type, 'ExpressionStatement');

  // Конвертируем в целое число имеющийся в 'rax' указатель
  visit.call(this, ast.body[0].expression);

  // Получаем число с плавающей запятой из числа кучи
  this.movq('xmm1', ['rax', 8]);

  // Округляем до нуля
  this.roundsd('zero', 'xmm1', 'xmm1');

  // Конвертируем double в целое число
  this.cvtsd2si('rax', 'xmm1');
}

function visitLiteral(ast) {
  assert.equal(typeof ast.value, 'number');

  // Выделяем новое число кучи
  this.stub('rax', 'Alloc');

  // Сохраняем 'rbx' регистр
  this.spill('rbx', function() {
    this.loadDouble('rbx', ast.value);
    this.mov(['rax', 8], 'rbx');
  });
}

function visitBinary(ast) {
  // Сохраняем 'rbx' после того, как вышли из AST узла
  this.spill('rbx', function() {
    // Посещаем правую сторону выражения
    visit.call(this, ast.right);

    // Перемещаем в 'rbx'
    this.mov('rbx', 'rax');

    // Посещаем левую сторону выражения (результат в 'rax')
    visit.call(this, ast.left);

    //
    // Обобщяя, левая сторона хранится в 'rax', правая - в 'rbx'
    //

    // Загружаем их значения в double представлении
    this.movq('xmm1', ['rax', 8]);
    this.movq('xmm2', ['rbx', 8]);

    // Выполняем бинарную операцию
    if (ast.operator === '+') {
      this.addsd('xmm1', 'xmm2');
    } else if (ast.operator === '-') {
      this.subsd('xmm1', 'xmm2');
    } else if (ast.operator === '*') {
      this.mulsd('xmm1', 'xmm2');
    } else if (ast.operator === '/') {
      this.divsd('xmm1', 'xmm2');
    } else {
      throw new Error('Неподдерживаемый бинарный оператор: ' + ast.operator);
    }

    // Выделяем новое число и складываем в него значение
    this.stub('rax', 'Alloc');
    this.movq(['rax', 8], 'xmm1');
  });
}

function visitUnary(ast) {
  if (ast.operator === '-') {
    // Делаем аргумент отрицательным эмулируя бинарное выражение
    visit.call(this, {
      type: 'BinaryExpression',
      operator: '*',
      left: ast.argument,
      right: { type: 'Literal', value: -1 }
    })
  } else {
    throw new Error('Unsupported unary operator: ' + ast.operator);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>smi и double представления [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:28 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/smi-double-2a9c</link>
      <guid>https://dev.to/scumdograscalhouse/smi-double-2a9c</guid>
      <description>&lt;p&gt;Вольный перевод статьи Fedor Indutny "&lt;a href="https://darksi.de/6.smis-and-doubles/"&gt;SMIs and Doubles&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  SMI и Double представления
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Цели
&lt;/h2&gt;

&lt;p&gt;В прошлой статье мы реализовали простой распределитель памяти и научили наш компилятор работать с числами с плавающей запятой, храня их в выделенных объектах кучи. Однако числа с плавающей запятой не подходят для некоторых точных операций, а также, поскольку они хранятся в памяти, что требует дополнительной загрузки и сохранения памяти, что снижает производительность кода.&lt;/p&gt;

&lt;p&gt;Обе проблемы можно решить с помощью поддержки целых чисел, как мы делали в первой статье, что значит что нам нужно внедрить поддержку обоих типов чисел в рантайме компилятора.&lt;/p&gt;

&lt;h2&gt;
  
  
  Отметки
&lt;/h2&gt;

&lt;p&gt;Вспомним, что мы храним указатели и числа в виде 64-х битных регистров общего назначения (&lt;code&gt;rax&lt;/code&gt;, &lt;code&gt;rbx&lt;/code&gt;, ...). Главная проблема в том, что мы должны иметь возможность определить, полученный регистр (например, &lt;code&gt;rax&lt;/code&gt;) имеет указатель на объект кучи или на само число (&lt;em&gt;SMI&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Обычно для такого используется метод "tagging". Есть &lt;a href="http://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations"&gt;несколько способов&lt;/a&gt; реализовать отметки, включая: &lt;a href="http://evilpie.github.io/sayrer-fatval-backup/cache.aspx.htm"&gt;Nan-Boxing&lt;/a&gt; (описание находится в блоке &lt;em&gt;Mozilla’s New JavaScript Value Representation&lt;/em&gt;) и Nun-Boxing. Нашему компилятору нужно будет просто зарезервировать один бит 64-х битного регистра и добавлять к нему &lt;code&gt;1&lt;/code&gt;, если значение является указателем, и  &lt;code&gt;0&lt;/code&gt;, если значение является &lt;em&gt;SMI&lt;/em&gt; (Small Integer).&lt;/p&gt;

&lt;p&gt;Пример:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iooJy3Xi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/smi-and-pointer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iooJy3Xi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/smi-and-pointer.png" alt="https://darksi.de/images/smi-and-pointer.png" width="352" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Заметьте что чотбы получить значение SMI нам потребуется сдвинуть его на один бит вправо (&lt;code&gt;&amp;gt;&amp;gt; 1&lt;/code&gt;) и конвертировать целое число в SMI - сдвиг влево (&lt;code&gt;&amp;lt;&amp;lt; 1&lt;/code&gt;). Использование нуля для отметки SMI очень окупается, поскольку нам не нужно размечать числа, чтобы выполнить сложение и вычитание.&lt;/p&gt;

&lt;p&gt;Чтобы использовать отмеченные указатели нам нужно найти значение находящееся на один бит левее фактического, что достаточно легко реализовать:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Предположим что отмеченный указатель находится в rbx
// И нам нужно загрузить его содержимое в rax
this.mov('rax', ['rbx', -1]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;И для удобства, пример непомеченных SMI:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Непомеченный
this.shr('rax', 1);
// Помеченный
this.shl('rax', 1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;И одна из главных частей - проверка является ли значение указателем:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Проверить, есть ли у'rax' последний бит
this.test('rax', 1);

// 'z' означает zero
// Перейти к метке, если `(rax &amp;amp; 1) == 0`
this.j('z', 'is-smi');

// 'nz' означает non-zero
// Перейти к метке, если `(rax &amp;amp; 1) != 0`
this.j('ne', 'is-heap-object-pointer');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Перерабатываем имеющийся код
&lt;/h2&gt;

&lt;p&gt;С помощью &lt;a href="https://github.com/indutny/jit.js/tree/master/example/heap"&gt;кода из прошлого поста&lt;/a&gt;, мы наконец можем приступить к реализации всех новых штук.&lt;/p&gt;

&lt;p&gt;В первую очередь, давайте добавим helper методы.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function untagSmi(reg) {
  this.shr(reg, 1);
};

function checkSmi(value, t, f) {
  // Если не были определены `true-` и `false-`
  // просто тестируем значение.
  if (!t &amp;amp;&amp;amp; !f)
    return this.test(value, 1);

  // Вводим новую область видимости, чтобы иметь возможность использовать именованные метки
  this.labelScope(function() {
    // Тестируем значение
    this.test(value, 1);

    // Пропускаем SMI случай, если результат non-zero
    this.j('nz', 'non-smi');

    // Запускаем SMI случай
    t.call(this);

    // Переходим к общему концу
    this.j('end');

    // Запускаем Non-SMI случай
    this.bind('non-smi');
    f.call(this);

    // Общий конец
    this.bind('end');
  });
};

function heapOffset(reg, offset) {
  // ПРИМЕЧАНИЕ: 8 - это размер указателя x64 архитектуры.
  // Добавляем 1 к смещению, поскольку на первом месте
  // хранится тип объектов кучи.
  return [reg, 8 * ((offset | 0) + 1) - 1];
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Мы можем включить эти методы в контекст jit.js, передав их как свойство &lt;code&gt;helpers&lt;/code&gt; в API метод &lt;code&gt;jit.compile()&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var helpers = {
  untagSmi: untagSmi,
  checkSmi: checkSmi,
  heapOffset: heapOffset
};

jit.compile(function() {
  // Здесь мы можем использовать хелперы:
  this.untagSmi('rax');

  this.checkSmi('rbx', function() {
    // Здесь работаем со SMI
  }, function() {
    // Здесь работаем с указателями
  });

  this.mov(this.heapOffset('rbx', 0), 1);
}, { stubs: stubs, helpers: helpers });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Назначение
&lt;/h2&gt;

&lt;p&gt;Сейчас нам нужно сделать чтобы наш стаб &lt;code&gt;Alloc&lt;/code&gt; вовзращал помеченный указатель. Также мы используем возможность и немного улучшим его с помощью добавления аргументов &lt;code&gt;tag&lt;/code&gt; и &lt;code&gt;size&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stubs.define('Alloc', function(size, tag) {
  // Сохраняем регистры 'rbx' и 'rcx'
  this.spill(['rbx', 'rcx'], function() {
    // Загружаем `offset`
    //
    // ПРИМЕЧАНИЕ: Мы будем использовать указатель для переменной `offset`, чтобы была вохможность
    // позже обновить ее
    this.mov('rax', this.ptr(offset));
    this.mov('rax', ['rax']);

    // Конец загрузки
    //
    // ПРИМЕЧАНИЕ: То же самое относится и к концу страницы, но мы не обновляем его прямо сейчас
    this.mov('rbx', this.ptr(end));
    this.mov('rbx', ['rbx']);

    // Вычисляем новый `offset`
    this.mov('rcx', 'rax');

    // Добавляем размер тега и тела
    this.add('rcx', 8);
    this.add('rcx', size);

    // Проверяем, не переполняется ли буффер фиксированного размера
    this.cmp('rcx', 'rbx');

    // this.j() выполняет условный переход к указанной метке..
    // 'g' означает 'greater'
    // 'overflow' - это имя метки, связанной ниже
    this.j('g', 'overflow');

    // Окей, может двигаться дальше и обновить offset
    this.mov('rbx', this.ptr(offset));
    this.mov(['rbx'], 'rcx');

    // Первый 64-х битный указатель зарезервирован для 'tag',
    // второй имеет значение `double`
    this.mov('rcx', tag);
    this.mov(['rax'], 'rcx');

    // !!!!!!!!!!!!!!!!!!!
    // ! Указатель метки !
    // !!!!!!!!!!!!!!!!!!!
    this.or('rax', 1);

    // Возвращаем 'rax'
    this.Return();

    // Переполнение :(
    this.bind('overflow')

    // Вызываем javascript функцию!
    // ПРИМЕЧАНИЕ: Штука ниже очень прикольная, но я поясню ее позже
    this.runtime(function() {
      console.log('GC is needed, but not implemented');
    });

    // Краш
    this.int3();

    this.Return();
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Математические стабы
&lt;/h2&gt;

&lt;p&gt;Также нам придется немного доработать математические операции для поддержки SMI и  double значений, давайте пока разделим их и добавим код который обрабатывает double:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var operators = ['+', '-', '*', '/'];
var map = { '+': 'addsd', '-': 'subsd', '*': 'mulsd',
            '/': 'divsd' };

// Определяем `Binary+`, `Binary-`, `Binary*` и `Binary/` стабы
operators.forEach(function(operator) {
  stubs.define('Binary' + operator, function(left, right) {
    // Сохраняем 'rbx' и 'rcx'
    this.spill(['rbx', 'rcx'], function() {
      // Загружаем аргументы для rax и rbx
      this.mov('rax', left);
      this.mov('rbx', right);

      // Конвертируем оба числа в double
      [['rax', 'xmm1'], ['rbx', 'xmm2']].forEach(function(regs) {
        var nonSmi = this.label();
        var done = this.label();

        this.checkSmi(regs[0]);
        this.j('nz', nonSmi);

        // Конвертируем целое число в double
        this.untagSmi(regs[0]);
        this.cvtsi2sd(regs[1], regs[0]);

        this.j(done);
        this.bind(nonSmi);

        this.movq(regs[1], this.heapOffset(regs[0], 0));
        this.bind(done);
      }, this);

      var instr = map[operator];

      // Выполняем бинарную операцию
      if (instr) {
        this[instr]('xmm1', 'xmm2');
      } else {
        throw new Error('Unsupported binary operator: ' +
                        operator);
      }

      // Выделяем новое число и складываем в него значение
      // ПРИМЕЧАНИЕ: Последние два аргумента являются
      // аргументами стаба (`size` и `tag`)
      this.stub('rax', 'Alloc', 8, 1);
      this.movq(this.heapOffset('rax', 0), 'xmm1');
    });

    this.Return();
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Обратите внимание, что стаб также преобразует все входящие числа в double.&lt;/p&gt;

&lt;h2&gt;
  
  
  Компилятор
&lt;/h2&gt;

&lt;p&gt;И сснова к коду компилятора:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function visitProgram(ast) {
  assert.equal(ast.body.length,
               1,
               'Only one statement programs are supported');
  assert.equal(ast.body[0].type, 'ExpressionStatement');

  // Конвертируем в целое число имеющийся в 'rax' указатель
  visit.call(this, ast.body[0].expression);

  // Получаем число с плавающей запятой из числа кучи
  this.checkSmi('rax', function() {
    // Убираем метку smi
    this.untagSmi('rax');
  }, function() {
    this.movq('xmm1', this.heapOffset('rax', 0));

    // Округляем до нуля
    this.roundsd('zero', 'xmm1', 'xmm1');

    // Конвертируем double в целое число
    this.cvtsd2si('rax', 'xmm1');
  });
}

function visitLiteral(ast) {
  assert.equal(typeof ast.value, 'number');

  if ((ast.value | 0) === ast.value) {
    // SMI, отмеченное значение
    // (т.е. val * 2) с последним битом, приведенным к нулю
    this.mov('rax', utils.tagSmi(ast.value));
  } else {
    // Выделяем новое число кучи
    this.stub('rax', 'Alloc', 8, 1);

    // Сохраняем 'rbx' регистр
    this.spill('rbx', function() {
      this.loadDouble('rbx', ast.value);

      // ПРИМЕЧАНИЕ: Последний бит указателей приведен к единице
      // Поэтому нам нужно использовать процедуру 'heapOffset'
      // чтобы получить доступ к памяти
      this.mov(this.heapOffset('rax', 0), 'rbx');
    });
  }
}

function visitBinary(ast) {
  // Сохраняем 'rbx' после того, как вышли из AST узла
  this.spill('rbx', function() {
    // Посещаем правую сторону выражения
    visit.call(this, ast.right);

    // Перемещаем в 'rbx'
    this.mov('rbx', 'rax');

    // Посещаем левую сторону выражения (результат в 'rax')
    visit.call(this, ast.left);

    //
    // Обобщяя, левая сторона хранится в 'rax', правая - в 'rbx'
    //

    if (ast.operator === '/') {
      // Вызываем стаб для операции деления
      this.stub('rax', 'Binary' + ast.operator, 'rax', 'rbx');
    } else {
      this.labelScope(function() {
        // Проверяем, являются ли оба числа SMI
        this.checkSmi('rax');
        this.j('nz', 'call stub');
        this.checkSmi('rbx');
        this.j('nz', 'call stub');

        // Сохраняем rax в случае смещения
        this.mov('rcx', 'rax');

        // ПРИМЕЧАНИЕ: В этой точке оба 'rax'и 'rbx' являются отмеченными.
        // Метки не нужно убирать, если мы выполняем операции
        // сложения и вычитания. Однако, в случае с
        // умножением, результат будет вдвое больше, если
        // не убрать метки.
        if (ast.operator === '+') {
          this.add('rax', 'rbx');
        } else if (ast.operator === '-') {
          this.sub('rax', 'rbx');
        } else if (ast.operator === '*') {
          this.untagSmi('rax');
          this.mul('rbx');
        }

        // При переполнении восстановить 'rax' из 'rcx' и вызвать стаб
        this.j('o', 'restore');

        // Иначе вернуть'rax'
        this.j('done');
        this.bind('restore');

        this.mov('rax', 'rcx');

        this.bind('call stub');

        // Вызвать стаб и вернуть номер кучи в 'rax'
        this.stub('rax', 'Binary' + ast.operator, 'rax', 'rbx');

        this.bind('done');
      });
    }
  });
}

function visitUnary(ast) {
  if (ast.operator === '-') {
    // Делаем аргумент отрицательным эмулируя бинарное выражение
    visit.call(this, {
      type: 'BinaryExpression',
      operator: '*',
      left: ast.argument,
      right: { type: 'Literal', value: -1 }
    })
  } else {
    throw new Error('Unsupported unary operator: ' + ast.operator);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Подведем итоги: теперь мы можем работать с SMI значениями по умолчанию, внедряя ради скорости дополнительные операции, и возвращаться к double значениям в случае переполнения или любых других проблем, вроде попытки найти сумму значений double и SMI!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>море узлов [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:26 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/-46b3</link>
      <guid>https://dev.to/scumdograscalhouse/-46b3</guid>
      <description>&lt;p&gt;Вольный перевод статьи Fedor Indutny "&lt;a href="https://darksi.de/d.sea-of-nodes/"&gt;Sea of Nodes&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Море Узлов
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Компиляторы &lt;strong&gt;= переводчики&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Компиляторы это такая вещь, которую каждый разработчик использует несколько раз на дню.  К удивлению, даже люди, совсем с кодом не знакомые, также пользуются компляторами. Это потому, что почти весь веб зависит от выполнения кода на стороне клиента и множество подобных программ передаются браузеру в виде исходного кода.&lt;/p&gt;

&lt;p&gt;Так мы подходим к важному тезису: пока исходный код читаем человеком, он выглядит настоящим мусором для CPU вашего ноутбука/компьютера/телефона/... . С другой стороны, машинный код, который кмоптютеры &lt;strong&gt;могут&lt;/strong&gt; читать, почти всегда нечитаем человеком. С этим нудно что-то делать, и решением является процесс под названием &lt;strong&gt;перевод (translation)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Тривиальные компиляторы выполняют один проход перевода: от исходного кода в машинный. Однако, на практике большинство компиляторов выполняют как минимум два прохода: от исходного кода к абстрактному синтаксическому дереву (&lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree"&gt;Abstract Syntax Tree&lt;/a&gt;), и от него к машинному коду. &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree"&gt;AST&lt;/a&gt; в этом случае выступает в качестве &lt;em&gt;Промежуточного Представления&lt;/em&gt; (Intermediate Representation), и, как следует из названия, является одним из форматов исходного кода. Эти промежуточные представление связаны друг с другом и, по сути, представляют собой не что иное, как уровни абстракции.&lt;/p&gt;

&lt;p&gt;Ограничения на количество уровней нет. Каждый новый слой приближает исходную программу к тому, как она будет выглядет в машинном коде.&lt;/p&gt;

&lt;h2&gt;
  
  
  Уровни оптимизации
&lt;/h2&gt;

&lt;p&gt;Однако, не все слои используются для одного только перевода. Множество компиляторов также дополнительно пытаются оптимизировать рукописный код. (Который обычно пишется для баланса между элегантностью кода и его производительностью).&lt;/p&gt;

&lt;p&gt;В качестве примера рассмотрим следующий JavaScript код:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (var i = 0, acc = 0; i &amp;lt; arr.length; i++)
  acc += arr[i];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Если компилятор переведет его в машинный код прямиком из &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree"&gt;AST&lt;/a&gt;, то выйдет что-то вроде этого (очень абстрактно и отдаленно от реальности):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acc = 0;
i = 0;
loop {
  // Load `.length` field of arr
  tmp = loadArrayLength(arr);
  if (i &amp;gt;= tmp)
    break;

  // Check that `i` is between 0 and `arr.length`
  // (NOTE: This is necessary for fast loads and
  // stores).
  checkIndex(arr, i);

  // Load value
  acc += load(arr, i);

  // Increment index
  i += 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Может быть неочевидно, но этот код далек от оптимального. Длина массива внутри цикла не изменяется, и проверять диапазон совсем не обязательно. В идеале, все должно выглядеть вот так:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acc = 0;
i = 0;
len = loadArrayLength(arr);
loop {
  if (i &amp;gt;= tmp)
    break;

  acc += load(arr, i);
  i += 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Давайте представим, как мы могли бы это сделать.&lt;/p&gt;

&lt;p&gt;Допустим, что под рукой у нас уже есть &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree"&gt;AST&lt;/a&gt;, и мы пытаемся сгенерировать из него машинный код:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(ПРИМЕЧАНИЕ: Сгенерировано с помощью &lt;a href="https://github.com/jquery/esprima"&gt;esprima&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ type: 'ForStatement',

  //
  // This is `var i = 0;`
  //
  init:
   { type: 'VariableDeclaration',
     declarations:
      [ { type: 'VariableDeclarator',
          id: { type: 'Identifier', name: 'i' },
          init: { type: 'Literal', value: 0, raw: '0' } },
        { type: 'VariableDeclarator',
          id: { type: 'Identifier', name: 'acc' },
          init: { type: 'Literal', value: 0, raw: '0' } }],
     kind: 'var' },

  //
  // `i &amp;lt; arr.length`
  //
  test:
   { type: 'BinaryExpression',
     operator: '&amp;lt;',
     left: { type: 'Identifier', name: 'i' },
     right:
      { type: 'MemberExpression',
        computed: false,
        object: { type: 'Identifier', name: 'arr' },
        property: { type: 'Identifier', name: 'length' } } },

  //
  // `i++`
  //
  update:
   { type: 'UpdateExpression',
     operator: '++',
     argument: { type: 'Identifier', name: 'i' },
     prefix: false },

  //
  // `arr[i] += 1;`
  //
  body:
   { type: 'ExpressionStatement',
     expression:
      { type: 'AssignmentExpression',
        operator: '+=',
        left: { type: 'Identifier', name: 'acc' },
        right:
         { type: 'MemberExpression',
           computed: true,
           object: { type: 'Identifier', name: 'arr' },
           property: { type: 'Identifier', name: 'i' } } } }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;JSON тоже можно визуализировать:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3HH3_KOj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/ast.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3HH3_KOj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/ast.svg" alt="https://darksi.de/images/ast.svg" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Это дерево, потому по нему можно удобно проходить сверху донизу, генерируя машинный код, когда посещаем его узлы. Проблема этого подхода в том, что информация о переменных очень скудна и распространяется по разным узлам дерева.&lt;/p&gt;

&lt;p&gt;Для безопасного перемещения поиска длины из цикла, мы, опять же, должны знать, что длина массива не меняется между его итерациями. Люди могут легко это сделать, взглянув на сам исходный код, но компилятору нужно неплохо поработать, чтобы извлечь эту информацию прямиком из AST.&lt;/p&gt;

&lt;p&gt;Как и большинство остальных компиляторных проблем, эта решается путем поднятия данных на более подходязий уровень абстракции, т.е. уровень промежуточного представления. Конкретно в нашем случае такой выбор ПП будет называться  потоковым графом (data-flow graph). Вместо того, чтобы говорить об сущностях синтаксиса (вроде &lt;code&gt;for loop&lt;/code&gt;, &lt;code&gt;expressions&lt;/code&gt;, ...), мы должны говорить о самих данных (чтение, значения переменных) и как они меняются через программу.&lt;/p&gt;

&lt;h2&gt;
  
  
  Потоковый граф
&lt;/h2&gt;

&lt;p&gt;В нашем примере данные в которых мы заинтересованы это значение переменной &lt;code&gt;arr&lt;/code&gt;. Мы хотим иметь возможность легко наблюдать за всеми действиями, чтобы убедиться нет ли у него доступа за пределы допустимого диапазона или каких-либо других изменений, который могли бы изменить длину массива.&lt;/p&gt;

&lt;p&gt;Этого можно достигнуть путем введения "def-use" (определение и использования) поведения между разными значениями данных. Это значит, что значение было объявлено один раз (узел) и было использовано для создания новых значений (граница для каждого использования). Очевидно, объединение разных значений сформирует &lt;strong&gt;потоковый граф&lt;/strong&gt;, вроде такого:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DpJp5_z3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/data-flow.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DpJp5_z3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/data-flow.svg" alt="https://darksi.de/images/data-flow.svg" width="800" height="760"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Обратите внимание на красный блок &lt;code&gt;array&lt;/code&gt;. Стрелки, выходящие из него, представляют использования его значения. Перебирая эти границы, компилятор может определить, что значение &lt;code&gt;array&lt;/code&gt; использовалось в:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;loadArrayLength&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkIndex&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;load&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Подобные графы строятся так, чтобы явно "клонировать" узел массива, если к его значению был получен деструктивный доступ. (т.е. хранилища, размеры длины). Где бы мы не наблюдали &lt;code&gt;array&lt;/code&gt;, в узле или его использованиях, мы всегда должны быть уверены, что его значение не изменяется.&lt;/p&gt;

&lt;p&gt;Может звучать сложно, но этого свойства графа достаточно легко достичь, он должен следовать правилам &lt;a href="https://en.wikipedia.org/wiki/Static_single_assignment_form"&gt;Single Static Assignment&lt;/a&gt; (SSA). Вкратце, чтобы конвертировать любую программу в &lt;a href="https://en.wikipedia.org/wiki/Static_single_assignment_form"&gt;SSA&lt;/a&gt;, компилятору нужно переименовать все присваивания и последующие использования переменных, чтобы убедиться что каждая переменная была определена единожды.&lt;/p&gt;

&lt;p&gt;Пример до SSA:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var a = 1;
console.log(a);
a = 2;
console.log(a);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;После SSA:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var a0 = 1;
console.log(a0);
var a1 = 2;
console.log(a1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Таким образом, мы, говоря об &lt;code&gt;a0&lt;/code&gt;, можем быть уверены, что на самом деле говорим об одном присвоении. Это близко к тому, как люди делают штуки на функциональных языках!&lt;/p&gt;

&lt;p&gt;Видя, что &lt;code&gt;loadArrayLength&lt;/code&gt;  не имеет управляющих зависимостей (i.e. нет пунктирных линий; мы поговорим о них позже), компилятор может заключить, что этот узел можно переместить в любое другое место вне цикла. Проходя по графу глубже, мы можем наблюдать что значение  узла &lt;code&gt;ssa:phi&lt;/code&gt; всегда находится между &lt;code&gt;0&lt;/code&gt; и &lt;code&gt;arr.length&lt;/code&gt;, потому &lt;code&gt;checkIndex&lt;/code&gt; может быть удален польностью.&lt;/p&gt;

&lt;p&gt;Довольно аккуратно, не так ли?&lt;/p&gt;

&lt;h2&gt;
  
  
  Граф потока управления
&lt;/h2&gt;

&lt;p&gt;Представление потока данных может быть очень полезным во многих случаях, но из его проблем заключается в том, что, превращая над код в граф такого вида, мы делаем шаг назад в нашей цепочке представлений (от исходного кода к машинному). Это промежуточное представление для генерации машинного  подходит даже меньше, чем AST.&lt;/p&gt;

&lt;p&gt;Причина в том что машина представляет собой просто последовательный список инструкций, который CPU выполняет один за другим. Наш финальный граф не отображает этого. По сути, принудительного упорядочивания узлов в нем вообще нет.&lt;/p&gt;

&lt;p&gt;Обычно это решается группировкой узлов графа в блоки. Такое представление известно как &lt;a href="https://en.wikipedia.org/wiki/Control_flow_graph"&gt;граф потока управления&lt;/a&gt; (&lt;a href="https://en.wikipedia.org/wiki/Control_flow_graph"&gt;Control Flow  Graph&lt;/a&gt;). Пример:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;b0 {
  i0 = literal 0
  i1 = literal 0

  i3 = array
  i4 = jump ^b0
}
b0 -&amp;gt; b1

b1 {
  i5 = ssa:phi ^b1 i0, i12
  i6 = ssa:phi ^i5, i1, i14

  i7 = loadArrayLength i3
  i8 = cmp "&amp;lt;", i6, i7
  i9 = if ^i6, i8
}
b1 -&amp;gt; b2, b3
b2 {
  i10 = checkIndex ^b2, i3, i6
  i11 = load ^i10, i3, i6
  i12 = add i5, i11
  i13 = literal 1
  i14 = add i6, i13
  i15 = jump ^b2
}
b2 -&amp;gt; b1

b3 {
  i16 = exit ^b3
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Он называется графом не просто так. Например, блоки &lt;code&gt;bXX&lt;/code&gt; представляют узлы, а стрелки &lt;code&gt;bXX -&amp;gt; bYY&lt;/code&gt; представляют границы. Давайте визуализируем это:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wddT9rhV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/cfg.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wddT9rhV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/cfg.svg" alt="https://darksi.de/images/cfg.svg" width="279" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Как вы можете видеть, перед циклом в блоке &lt;code&gt;b0&lt;/code&gt; есть код, а также заголовок цилка в &lt;code&gt;b1&lt;/code&gt;, тест цикла в &lt;code&gt;b2&lt;/code&gt;, тело цикла в &lt;code&gt;b3&lt;/code&gt; и выход из узла в &lt;code&gt;b4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Перевод в машинный код становится очень простым благодаря из такого формату. Мы просто заменяем идентификаторы &lt;code&gt;iXX&lt;/code&gt; на имена регистров CPU (в каком-то смысле, геристры CPU являются своего рода переменными, CPU имеет ограниченное количество регистров, потому нам надо быть осторожными чтоб они не закончились), и построчно генерируем машинный код для каждой инструкции.&lt;/p&gt;

&lt;p&gt;Подводя итоги, &lt;a href="https://en.wikipedia.org/wiki/Control_flow_graph"&gt;CFG&lt;/a&gt; имеет связи потоков данных и упорядоченность. Это позволяет нам использовать его как для анализа данных, так и для генерации машинного кода. Однако, попытка оптимизировать CFG путем манипуляций над блоками и их содержимым, может быстро усложниться и стать подверженной ошибкам.&lt;/p&gt;

&lt;p&gt;Вместо того, Clifford Click и Keith D. Cooper предложили использовать подход, называемый &lt;strong&gt;&lt;a href="http://www.researchgate.net/profile/Cliff_Click/publication/2394127_Combining_Analyses_Combining_Optimizations/links/0a85e537233956f6dd000000.pdf"&gt;морем узлов (sea-of-nodes)&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Море узлов (&lt;strong&gt;Sea-of-Nodes)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Помните наш крутой потоковый граф с пунктриными линиями? Эти пунктирные линии как раз таки делают потоковый граф &lt;strong&gt;графом моря узлов&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Вместо группировки в блоки и упорядочивания узлов, мы решили объявить управляющие зависимости в графе, как пунктриные границы. Если мы удалим из графа все &lt;strong&gt;непунктирные&lt;/strong&gt; штуки и немного сгруппируем все, то получим:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JX0XIkqM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/control-flow-sea.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JX0XIkqM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/control-flow-sea.svg" alt="https://darksi.de/images/control-flow-sea.svg" width="800" height="1645"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Добавим немного воображения и порядка узлам, мы сможем увидеть что этот граф является тем же самым что и упрощенный CFG граф, окторый мы видели выше:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wddT9rhV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/cfg.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wddT9rhV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/cfg.svg" alt="https://darksi.de/images/cfg.svg" width="279" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Давайте еще раз вглянем на представление &lt;strong&gt;sea-of-nodes&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DpJp5_z3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/data-flow.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DpJp5_z3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://darksi.de/images/data-flow.svg" alt="https://darksi.de/images/data-flow.svg" width="800" height="760"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Разительная разница между этим и CFG графом в том, что здесь нет упорядочивания узлой, кроме тех, которые имеют управляющие зависимости (другими словами, узлы участвуют в в потоке управления).&lt;/p&gt;

&lt;p&gt;Это представление - очень мощный способ взглянуть на код. Он знает все об общем графике потока данных и может быть легко изменен без постоянного удаления / замены узлов в блоках.&lt;/p&gt;

&lt;h2&gt;
  
  
  Сокращения
&lt;/h2&gt;

&lt;p&gt;Говоря об изменениях, давайте обсудим, как изменить график. Граф «море узлов» обычно модифицируется путем сокращения графа. Мы просто ставим в очередь все узлы на графике и вызываем нашу функцию сокращения для каждого узла в очереди. Все, к чему притрагивается эта функция (изменяет, заменяет), ставится  назад в очередь и будет передано функции позже. Если у вас есть много сокращений, вы можете просто сложить их вместе и вызвать их все на каждом узле в очереди, или, альтернативно, вы можете просто применить их один за другим, если они зависят от конечного состояния друг друга. Работает прелестно!&lt;/p&gt;

&lt;p&gt;Я написал набор инструментов для JavaScript для моих экспериментов с sea-of-nodes, который включает:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/indutny/json-pipeline"&gt;json-pipeline&lt;/a&gt; - сборщик и stdlib графа. Предоставляет методы для создания узлов, добавления им входных данных, изменения их зависимостей и экспорта/импорта графа из/в  данные!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/indutny/json-pipeline-reducer"&gt;json-pipeline-reducer&lt;/a&gt; - движок сокращений. Просто создаем экземпляр редуктора, скармливаем ему функции для сокращения и выполняем редьюсер на уже существующем графе &lt;a href="https://github.com/indutny/json-pipeline"&gt;json-pipeline&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/indutny/json-pipeline-scheduler"&gt;json-pipeline-scheduler&lt;/a&gt; - библиотека для возврата неупорядоченного графа в ограниченном количестве блоков, соединенных между собой управляющими ребрами (пунктирными линиями).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Объединенные вместе, эти инструменты могут решить множество проблем, который могут быть сформулированы в терминах уравнений потока данных.&lt;/p&gt;

&lt;p&gt;Пример сокращения, который будет оптимизировать наш исходный JS код:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for (var i = 0, acc = 0; i &amp;lt; arr.length; i++)&lt;br&gt;
  acc += arr[i];&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  &lt;strong&gt;TL;DR&lt;/strong&gt;&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Этот кусок кода достаточно велик, потому если хотите пропустить его - вот вам заметки о том, что мы делаем с его помощью:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Вычисляем целочисленные диапазоны различных узлов: literal, add, phi&lt;/li&gt;
&lt;li&gt;Вычисляем лимиты тела ветки&lt;/li&gt;
&lt;li&gt;Применяем диапазон и ограничиваем информацию (&lt;code&gt;i&lt;/code&gt; всегда будет неотрицательным числом, ограниченным &lt;code&gt;arr.length&lt;/code&gt;) , чтобы сделать вывод, что проверка длины не нужна может быть удалена&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;arr.length&lt;/code&gt; будет убран из цикла автоматически с помощью &lt;code&gt;json-pipeline-scheduler&lt;/code&gt;. Потому что для планировки узлов в блоках он совершает &lt;a href="https://courses.cs.washington.edu/courses/cse501/04wi/papers/click-pldi95.pdf"&gt;Global Code Motion&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;// Для просмотра вывода graphviz&lt;br&gt;
var fs = require('fs');&lt;/p&gt;

&lt;p&gt;var Pipeline = require('json-pipeline');&lt;br&gt;
var Reducer = require('json-pipeline-reducer');&lt;br&gt;
var Scheduler = require('json-pipeline-scheduler');&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Создаем пустой граф с удобными методами CFG.&lt;br&gt;
//&lt;br&gt;
var p = Pipeline.create('cfg');&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Парсим данные и генерируем граф.&lt;br&gt;
//&lt;br&gt;
p.parse(`pipeline {&lt;br&gt;
  b0 {&lt;br&gt;
    i0 = literal 0&lt;br&gt;
    i1 = literal 0&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i3 = array
i4 = jump ^b0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;br&gt;
  b0 -&amp;gt; b1&lt;/p&gt;

&lt;p&gt;b1 {&lt;br&gt;
    i5 = ssa:phi ^b1 i0, i12&lt;br&gt;
    i6 = ssa:phi ^i5, i1, i14&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i7 = loadArrayLength i3
i8 = cmp "&amp;lt;", i6, i7
i9 = if ^i6, i8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;br&gt;
  b1 -&amp;gt; b2, b3&lt;br&gt;
  b2 {&lt;br&gt;
    i10 = checkIndex ^b2, i3, i6&lt;br&gt;
    i11 = load ^i10, i3, i6&lt;br&gt;
    i12 = add i5, i11&lt;br&gt;
    i13 = literal 1&lt;br&gt;
    i14 = add i6, i13&lt;br&gt;
    i15 = jump ^b2&lt;br&gt;
  }&lt;br&gt;
  b2 -&amp;gt; b1&lt;/p&gt;

&lt;p&gt;b3 {&lt;br&gt;
    i16 = exit ^b3&lt;br&gt;
  }&lt;br&gt;
}`, { cfg: true }, 'printable');&lt;/p&gt;

&lt;p&gt;if (process.env.DEBUG)&lt;br&gt;
  fs.writeFileSync('before.gv', p.render('graphviz'));&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Создаем хелпер для сокращений&lt;br&gt;
//&lt;/p&gt;

&lt;p&gt;function reduce(graph, reduction) {&lt;br&gt;
  var reducer = new Reducer();&lt;br&gt;
  reducer.addReduction(reduction);&lt;br&gt;
  reducer.reduce(graph);&lt;/p&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Создаем сокращение&lt;br&gt;
//&lt;br&gt;
var ranges = new Map();&lt;/p&gt;

&lt;p&gt;function getRange(node) {&lt;br&gt;
  if (ranges.has(node))&lt;br&gt;
    return ranges.get(node);&lt;/p&gt;

&lt;p&gt;var range = { from: -Infinity, to: +Infinity, type: 'any' };&lt;br&gt;
  ranges.set(node, range);&lt;br&gt;
  return range;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function updateRange(node, reducer, from, to) {&lt;br&gt;
  var range = getRange(node);&lt;/p&gt;

&lt;p&gt;// Самый низкий тип&lt;br&gt;
  if (range.type === 'none')&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;if (range.from === from &amp;amp;&amp;amp; range.to === to &amp;amp;&amp;amp; range.type === 'int')&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;range.from = from;&lt;br&gt;
  range.to = to;&lt;br&gt;
  range.type = 'int';&lt;br&gt;
  reducer.change(node);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function updateType(node, reducer, type) {&lt;br&gt;
  var range = getRange(node);&lt;/p&gt;

&lt;p&gt;if (range.type === type)&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;range.type = type;&lt;br&gt;
  reducer.change(node);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Устанавливаем тип литерала&lt;br&gt;
//&lt;br&gt;
function reduceLiteral(node, reducer) {&lt;br&gt;
  var value = node.literals[0];&lt;br&gt;
  updateRange(node, reducer, value, value);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function reduceBinary(node, left, right, reducer) {&lt;br&gt;
  if (left.type === 'none' || right.type === 'none') {&lt;br&gt;
    updateType(node, reducer, 'none');&lt;br&gt;
    return false;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if (left.type === 'int' || right.type === 'int')&lt;br&gt;
    updateType(node, reducer, 'int');&lt;/p&gt;

&lt;p&gt;if (left.type !== 'int' || right.type !== 'int')&lt;br&gt;
    return false;&lt;/p&gt;

&lt;p&gt;return true;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Объединяем диапазоны входных данных&lt;br&gt;
//&lt;br&gt;
function reducePhi(node, reducer) {&lt;br&gt;
  var left = getRange(node.inputs[0]);&lt;br&gt;
  var right = getRange(node.inputs[1]);&lt;/p&gt;

&lt;p&gt;if (!reduceBinary(node, left, right, reducer))&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;if (node.inputs[1].opcode !== 'add' || left.from !== left.to)&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;var from = Math.min(left.from, right.from);&lt;br&gt;
  var to = Math.max(left.to, right.to);&lt;br&gt;
  updateRange(node, reducer, from, to);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Находим phi = phi + , где начальный phi является числом,&lt;br&gt;
// сообщаем правильный диапазон.&lt;br&gt;
//&lt;br&gt;
function reduceAdd(node, reducer) {&lt;br&gt;
  var left = getRange(node.inputs[0]);&lt;br&gt;
  var right = getRange(node.inputs[1]);&lt;/p&gt;

&lt;p&gt;if (!reduceBinary(node, left, right, reducer))&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;var phi = node.inputs[0];&lt;br&gt;
  if (phi.opcode !== 'ssa:phi' || right.from !== right.to)&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;var number = right.from;&lt;br&gt;
  if (number &amp;lt;= 0 || phi.inputs[1] !== node)&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;var initial = getRange(phi.inputs[0]);&lt;br&gt;
  if (initial.type !== 'int')&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;updateRange(node, reducer, initial.from, +Infinity);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;var limits = new Map();&lt;/p&gt;

&lt;p&gt;function getLimit(node) {&lt;br&gt;
  if (limits.has(node))&lt;br&gt;
    return limits.get(node);&lt;/p&gt;

&lt;p&gt;var map = new Map();&lt;br&gt;
  limits.set(node, map);&lt;br&gt;
  return map;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function updateLimit(holder, node, reducer, type, value) {&lt;br&gt;
  var map = getLimit(holder);&lt;br&gt;
  if (!map.has(node))&lt;br&gt;
    map.set(node, { type: 'any', value: null });&lt;/p&gt;

&lt;p&gt;var limit = map.get(node);&lt;br&gt;
  if (limit.type === type &amp;amp;&amp;amp; limit.value === value)&lt;br&gt;
    return;&lt;br&gt;
  limit.type = type;&lt;br&gt;
  limit.value = value;&lt;br&gt;
  reducer.change(holder);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;function mergeLimit(node, reducer, other) {&lt;br&gt;
  var map = getLimit(node);&lt;br&gt;
  var otherMap = getLimit(other);&lt;/p&gt;

&lt;p&gt;otherMap.forEach(function(limit, key) {&lt;br&gt;
    updateLimit(node, key, reducer, limit.type, limit.value);&lt;br&gt;
  });&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Распространяем лимит от X &amp;lt; Y к правдивой ветке &lt;code&gt;if&lt;/code&gt;&lt;br&gt;
//&lt;br&gt;
function reduceIf(node, reducer) {&lt;br&gt;
  var test = node.inputs[0];&lt;br&gt;
  if (test.opcode !== 'cmp' || test.literals[0] !== '&amp;lt;')&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;var left = test.inputs[0];&lt;br&gt;
  var right = test.inputs[1];&lt;/p&gt;

&lt;p&gt;updateLimit(node.controlUses[0], left, reducer, '&amp;lt;', right);&lt;br&gt;
  updateLimit(node.controlUses[2], left, reducer, '&amp;gt;=', right);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Определяем диапазоны и границы значений.&lt;br&gt;
//&lt;/p&gt;

&lt;p&gt;var rangeAndLimit = new Reducer.Reduction({&lt;br&gt;
  reduce: function(node, reducer) {&lt;br&gt;
    if (node.opcode === 'literal')&lt;br&gt;
      reduceLiteral(node, reducer);&lt;br&gt;
    else if (node.opcode === 'ssa:phi')&lt;br&gt;
      reducePhi(node, reducer);&lt;br&gt;
    else if (node.opcode === 'add')&lt;br&gt;
      reduceAdd(node, reducer);&lt;br&gt;
    else if (node.opcode === 'if')&lt;br&gt;
      reduceIf(node, reducer);&lt;br&gt;
  }&lt;br&gt;
});&lt;br&gt;
reduce(p, rangeAndLimit);&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Теперь, когда у нас есть диапазоны и лимиты,&lt;br&gt;
// время удалить бесполезные проверки&lt;br&gt;
// длины массива.&lt;br&gt;
&lt;del&gt;/&lt;/del&gt;/&lt;/p&gt;

&lt;p&gt;function reduceCheckIndex(node, reducer) {&lt;br&gt;
  // Проходим по цепочке управления&lt;br&gt;
  var region = node.control[0];&lt;br&gt;
  while (region.opcode !== 'region' &amp;amp;&amp;amp; region.opcode !== 'start')&lt;br&gt;
    region = region.control[0];&lt;/p&gt;

&lt;p&gt;var array = node.inputs[0];&lt;br&gt;
  var index = node.inputs[1];&lt;/p&gt;

&lt;p&gt;var limit = getLimit(region).get(index);&lt;br&gt;
  if (!limit)&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;var range = getRange(index);&lt;/p&gt;

&lt;p&gt;// Делаем массив отрицательным, есть индекс невалиден&lt;br&gt;
  if (range.from &amp;lt; 0)&lt;br&gt;
    return;&lt;/p&gt;

&lt;p&gt;// Индекс должен быть ограничен длинной массива&lt;br&gt;
  if (limit.type !== '&amp;lt;' ||&lt;br&gt;
      limit.value.opcode !== 'loadArrayLength' ||&lt;br&gt;
      limit.value.inputs[0] !== array) {&lt;br&gt;
    return;&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Проверяем, безопасно ли удалять!&lt;br&gt;
  reducer.remove(node);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;var eliminateChecks = new Reducer.Reduction({&lt;br&gt;
  reduce: function(node, reducer) {&lt;br&gt;
    if (node.opcode === 'checkIndex')&lt;br&gt;
      reduceCheckIndex(node, reducer);&lt;br&gt;
  }&lt;br&gt;
});&lt;br&gt;
reduce(p, eliminateChecks);&lt;/p&gt;

&lt;p&gt;//&lt;br&gt;
// Запускаем планировщик, чтобы положить все &lt;br&gt;
// назад в CFG&lt;br&gt;
//&lt;/p&gt;

&lt;p&gt;var out = Scheduler.create(p).run();&lt;br&gt;
out.reindex();&lt;/p&gt;

&lt;p&gt;if (process.env.DEBUG)&lt;br&gt;
  fs.writeFileSync('after.gv', out.render('graphviz'));&lt;/p&gt;

&lt;p&gt;console.log(out.render({ cfg: true }, 'printable'));&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>ускоренные async функции и промисы [перевод]</title>
      <dc:creator>собачья будка</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:29:11 +0000</pubDate>
      <link>https://dev.to/scumdograscalhouse/async-4be3</link>
      <guid>https://dev.to/scumdograscalhouse/async-4be3</guid>
      <description>&lt;p&gt;Вольный перевод статьи Maya Lekova и Benedikt Meurer "&lt;a href="https://v8.dev/blog/fast-async" rel="noopener noreferrer"&gt;Faster async functions and promises&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Ускоренные async функции и промисы
&lt;/h1&gt;

&lt;p&gt;Традиционно, асинхронность в JavaScript имела репутацию не очень быстрой. Что еще хуже, лайв-отладка JavaScript приложений, в частности серверов Node.js, - непростая задача, &lt;em&gt;особенно&lt;/em&gt; когда речь идет об асинхронном программировании. К счастью, времена меняются. В этой статье описано, как мы оптимизировали асинхронные функции и промисы в V8 (и в других JS движках в некоторой степени) и улучшали отладку асинхронного кода.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Примечание:&lt;/strong&gt;  если тексту вы предпочитаете презентацию, ниже приложено видео.&lt;/p&gt;

&lt;p&gt;video &lt;a href="https://www.youtube.com/watch?v=DFP5DKDQfOc" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=DFP5DKDQfOc&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Новый подход к асинхронному программированию
&lt;/h2&gt;

&lt;h3&gt;
  
  
  От коллбэков до промисов и асинхронных функций
&lt;/h3&gt;

&lt;p&gt;Перед тем, как промисы стали частью JavaScript спецификации, для асинхронности обычно использовались API на основе коллбэков, особенно в Node.js. Вот пример:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function handler(done) {
  validateParams((error) =&amp;gt; {
    if (error) return done(error);
    dbQuery((error, dbResults) =&amp;gt; {
      if (error) return done(error);
      serviceCall(dbResults, (error, serviceResults) =&amp;gt; {
        console.log(result);
        done(error, serviceResults);
      });
    });
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Данный паттерн использует коллбэки глубокой вложенности, что обычно называют &lt;em&gt;“callback hell” (ад коллбэков)&lt;/em&gt;, поскольку такой метод усложняет поддержку и читабельность кода.&lt;/p&gt;

&lt;p&gt;К счастью, промисы теперь являются частью спецификации JavaScript, и тот же код можно сделать более элегантным и поддерживаемым:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function handler() {
  return validateParams()
    .then(dbQuery)
    .then(serviceCall)
    .then(result =&amp;gt; {
      console.log(result);
      return result;
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Совсем недавно JavaScript получил поддержку &lt;a href="https://developers.google.com/web/fundamentals/primers/async-functions" rel="noopener noreferrer"&gt;асинхронных функций&lt;/a&gt;. И тот же самый асинхронный код теперь очень схож с синхронным:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function handler() {
  await validateParams();
  const dbResults = await dbQuery();
  const results = await serviceCall(dbResults);
  console.log(results);
  return results;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;С асинхронными функциями код становится более коротким, и теперь гораздо легче следить и контролировать потоки данных, несмотря на то, что код выполняется асинхронно. (Заметьте, что выполнение JavaScript кода все еще происходит в одном потоке, потому асинхронные функции сами по себе не создают физических потоков.)&lt;/p&gt;

&lt;h3&gt;
  
  
  От обработки коллбэков event listener до асинхронной итерации
&lt;/h3&gt;

&lt;p&gt;Еще одна асинхронная парадигма, которая особенно часто используется в Node.js - &lt;code&gt;[ReadableStream](https://nodejs.org/api/stream.html#stream_readable_streams)&lt;/code&gt;. Вот пример:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const http = require('http');

http.createServer((req, res) =&amp;gt; {
  let body = '';
  req.setEncoding('utf8');
  req.on('data', (chunk) =&amp;gt; {
    body += chunk;
  });
  req.on('end', () =&amp;gt; {
    res.write(body);
    res.end();
  });
}).listen(1337);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Этот код может быть не очень понятным: входные данные обрабатываются чанками, которые доступны только внутри коллбэков, и уведомления об окончании потока тоже происходят внутри коллбэков. И, поскольку функция завершается сразу, а обработка происходит внутри коллбэков, очень легко наткнуться на баги.&lt;/p&gt;

&lt;p&gt;Упростить код поможет &lt;a href="https://2ality.com/2016/10/asynchronous-iteration.html" rel="noopener noreferrer"&gt;асинхронная итерация&lt;/a&gt; - новая крутая фича ES2018:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const http = require('http');

http.createServer(async (req, res) =&amp;gt; {
  try {
    let body = '';
    req.setEncoding('utf8');
    for await (const chunk of req) {
      body += chunk;
    }
    res.write(body);
    res.end();
  } catch {
    res.statusCode = 500;
    res.end();
  }
}).listen(1337);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Все эти новые фичи вы уже можете использовать в продакшене! Асинхронные функции &lt;strong&gt;полностью поддерживаются в Node.js 8 (V8 v6.2 / Chrome 62)&lt;/strong&gt;, а асинхронные итераторы и генераторы &lt;strong&gt;в Node.js 10 (V8 v6.8 / Chrome 68)&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Улучшение перформанса асинхронности
&lt;/h2&gt;

&lt;p&gt;Нам удалось существенно улучшить производительность асинхронного кода между V8 версии 5.5 (Chrome 55 &amp;amp; Node.js 7) и V8 версии 6.8 (Chrome 68 &amp;amp; Node.js 10). Мы достигли уровня производительности, когда разработчики могут без опаски и не беспокоясь о скорости пользоваться этими новыми парадигмами.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fdoxbee-benchmark.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fdoxbee-benchmark.svg" alt="https://v8.dev/_img/fast-async/doxbee-benchmark.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Диаграмма выше показывает замеры &lt;a href="https://github.com/v8/promise-performance-tests/blob/master/lib/doxbee-async.js" rel="noopener noreferrer"&gt;doxbee бенчмарка&lt;/a&gt;, который замеряет производительность нагруженного промисами кода. Обратите внимание, что график отображают время выполнения, то есть чем меньше, тем лучше.&lt;/p&gt;

&lt;p&gt;Результат &lt;a href="https://github.com/v8/promise-performance-tests/blob/master/lib/parallel-async.js" rel="noopener noreferrer"&gt;параллельного бенчмарка&lt;/a&gt;,  который специально стрессует производительность &lt;code&gt;[Promise.all()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)&lt;/code&gt;, еще более интересен:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fparallel-benchmark.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fparallel-benchmark.svg" alt="https://v8.dev/_img/fast-async/parallel-benchmark.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Мы смогли улучшить производительность &lt;code&gt;Promise.all&lt;/code&gt; &lt;strong&gt;в 8 раз&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Всё же, оба микро-теста были синтетическими, а команда V8 больше заинтересована в том, как оптимизация повлияла на &lt;a href="https://v8.dev/blog/real-world-performance" rel="noopener noreferrer"&gt;реальную производительность пользовательского кода&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fhttp-benchmarks.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fhttp-benchmarks.svg" alt="https://v8.dev/_img/fast-async/http-benchmarks.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;График выше отображает перформанс нескольких популярных  middleware HTTP фреймворков, которые плотно используют промисы и &lt;code&gt;async&lt;/code&gt; функции. Заметьте, что данный график, в отличие от прошлых, показывает количество запросов в секунду, потому чем больше, тем лучше. Производительность этих фреймворком была улучшена между Node.js 7 (V8 версии 5.5) и Node.js 10 (V8 версии 6.8).&lt;/p&gt;

&lt;p&gt;Такие улучшения производительности появились благодаря трем ключевым достижениям:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://v8.dev/docs/turbofan" rel="noopener noreferrer"&gt;TurboFan&lt;/a&gt;, новый оптимизирующий компилятор 🎉&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://v8.dev/blog/orinoco" rel="noopener noreferrer"&gt;Orinoco&lt;/a&gt;, новый сборщик мусора 🚛&lt;/li&gt;
&lt;li&gt;Исправлению бага Node.js 8, при котором &lt;code&gt;await&lt;/code&gt; пропускал микротики 🐛&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Мы &lt;a href="https://v8.dev/blog/launching-ignition-and-turbofan" rel="noopener noreferrer"&gt;запустили TurboFan&lt;/a&gt; в &lt;a href="https://medium.com/the-node-js-collection/node-js-8-3-0-is-now-available-shipping-with-the-ignition-turbofan-execution-pipeline-aa5875ad3367" rel="noopener noreferrer"&gt;Node.js 8&lt;/a&gt;, что дало огромный прирост в производительности.&lt;/p&gt;

&lt;p&gt;Также поработали над новым сборщиком мусора Orinoco, который исключает работу по сбору мусора из основного потока, что, в свою очередь, значительно улучшает обработку запросов.&lt;/p&gt;

&lt;p&gt;И последним по списку, но не по важности, было исправление бага Node.js 8, который приводил к тому, что в некоторых случаях &lt;code&gt;await&lt;/code&gt; пропускал микротики. Баг считался непреднамеренным нарушением спецификации, но позже дал нам идею, как улучшить производительность. Давайте начнем с объяснения багованного поведения:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const p = Promise.resolve();

(async () =&amp;gt; {
  await p; console.log('after:await');
})();

p.then(() =&amp;gt; console.log('tick:a'))
 .then(() =&amp;gt; console.log('tick:b'));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Код выше создает зарезолвенный промис &lt;code&gt;p&lt;/code&gt;, и ожидает его результата с помощью &lt;code&gt;await&lt;/code&gt;, но также создает цепочку из двух обработчиков. И в каком же порядке нам стоит ожидать выполнения &lt;code&gt;console.log&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Поскольку &lt;code&gt;p&lt;/code&gt; уже выполнен, вы можете ожидать, что первым в консоли появится &lt;code&gt;'after:await'&lt;/code&gt;, и только потом оба &lt;code&gt;'tick'&lt;/code&gt;. По факту, так себя и поведет Node.js 8:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-bug-node-8.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-bug-node-8.svg" alt="https://v8.dev/_img/fast-async/await-bug-node-8.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Это и есть &lt;code&gt;await&lt;/code&gt; баг Node.js 8&lt;/p&gt;

&lt;p&gt;Хоть такое поведение и кажется интуитивно понятным - оно не соответствует спецификации. Node.js 10 внедряет правильное поведение, которое сперва выполнит обработчики в цепочке, и только после продолжить асинхронную функцию.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-bug-node-10.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-bug-node-10.svg" alt="https://v8.dev/_img/fast-async/await-bug-node-10.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Node.js 10 избавился от &lt;code&gt;await&lt;/code&gt; бага&lt;/p&gt;

&lt;p&gt;Это &lt;em&gt;“правильное поведение”&lt;/em&gt;  может вызвать споры или удивить JavaScript разработчиков, а потому заслуживает некоторого пояснения. Давайте начнем с основ, прежде чем погрузимся в волшебный мир промисов и асинхронных функций.&lt;/p&gt;

&lt;h3&gt;
  
  
  Таски против микротасок
&lt;/h3&gt;

&lt;p&gt;На верхнем уровне JavaScript есть &lt;em&gt;таски&lt;/em&gt; и &lt;em&gt;микротаски&lt;/em&gt;. Первые обрабатывают события вроде I/O и таймеров, и выполняют их по одному в момент времени. Вторые внедряют отложенное выполнение &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; и промисов, и выполняют их в конце каждого таска. Очередь для микротасок всегда опустошается, прежде чем выполнение вернётся в цикл обработки событий.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fmicrotasks-vs-tasks.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fmicrotasks-vs-tasks.svg" alt="https://v8.dev/_img/fast-async/microtasks-vs-tasks.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Разница между тасками и микротасками&lt;/p&gt;

&lt;p&gt;Больше подробностей вы можете найти в статье Джейка Арчибальда "&lt;a href="https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/" rel="noopener noreferrer"&gt;таски, микротаски, очереди и планировки в браузере&lt;/a&gt;". Модель задач в Node.js очень схожа.&lt;/p&gt;

&lt;h3&gt;
  
  
  Асинхронные функции
&lt;/h3&gt;

&lt;p&gt;Как описывает MDN, асинхронная функция - это функция, которая подразумевает асинхронное возвращение промиса в качестве результата. Они предназначены для того, чтобы асинхронный код выглядел как синхронный, тем самым скрывая некоторые сложности асинхронной разработки от разработчика.&lt;/p&gt;

&lt;p&gt;Простейшая асинхронная функция выглядит следующим образом:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function computeAnswer() {
  return 42;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;После вызова она возвращает промис, данные которого можно получить как при работе с любым другим промисом.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const p = computeAnswer();
// → Промис

p.then(console.log);
// выведет 42 на следующем ходу
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Значение промиса &lt;code&gt;p&lt;/code&gt; вы получите тогда, когда отработают микротаски. Другими словами, код выше семантически равен использованию &lt;code&gt;Promise.resolve&lt;/code&gt; со значением:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function computeAnswer() {
  return Promise.resolve(42);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Но настоящая мощь асинхронных функций появляется при использовании выражения &lt;code&gt;await&lt;/code&gt;, которое тормозит выполнение функции, пока промис не будет зарезолвлен. Значением &lt;code&gt;await&lt;/code&gt; будет выполненный промис. Вот пример для пояснения:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function fetchStatus(url) {
  const response = await fetch(url);
  return response.status;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Выполнение &lt;code&gt;fetchStatus&lt;/code&gt; остановится на моменте &lt;code&gt;await&lt;/code&gt;, и продолжится только после того, как &lt;code&gt;fetch&lt;/code&gt; промис выполнится. Это в какой-то мере эквивалентно цепочке обработчиков промиса, который возвращает &lt;code&gt;fetch&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function fetchStatus(url) {
  return fetch(url).then(response =&amp;gt; response.status);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Обработчик выполняет те же действия, что и &lt;code&gt;await&lt;/code&gt; в асинхронной функции.&lt;/p&gt;

&lt;p&gt;Обычно, вы прокидываете &lt;code&gt;Promise&lt;/code&gt; в &lt;code&gt;await&lt;/code&gt;, но &lt;code&gt;await&lt;/code&gt; может ожидать и любого другого JavaScript значения, и, если это значение не является промисом, то будет в него преобразовано. Это означает, что вы можете ожидать даже число (&lt;code&gt;await 42&lt;/code&gt;), если нужно:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function foo() {
  const v = await 42;
  return v;
}

const p = foo();
// → Промис

p.then(console.log);
// в итоге выведет `42` 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Более интересно то, что &lt;code&gt;await&lt;/code&gt; может работать с &lt;a href="https://promisesaplus.com/" rel="noopener noreferrer"&gt;“thenable”&lt;/a&gt; значениями, т.е. с любым объектом с методом &lt;code&gt;then&lt;/code&gt;, даже если он не является настоящим промисом. Таким образом можно реализовывать веселые штуки, вроде асинхронного сна, который измеряет потраченное на него время:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) {
    const startTime = Date.now();
    setTimeout(() =&amp;gt; resolve(Date.now() - startTime),
               this.timeout);
  }
}

(async () =&amp;gt; {
  const actualTime = await new Sleep(1000);
  console.log(actualTime);
})();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Давайте посмотрим, как V8 обрабатывает &lt;code&gt;await&lt;/code&gt; под капотом, следуя &lt;a href="https://tc39.es/ecma262/#await" rel="noopener noreferrer"&gt;спецификации&lt;/a&gt;. Возьмем простой пример асинхронной функции &lt;code&gt;foo&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function foo(v) {
  const w = await v;
  return w;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;После вызова функции, параметр &lt;code&gt;v&lt;/code&gt; оборачивается в промис и выполнение функции останавливается, пока промис не будет зарезолвлен. Как только это произойдет, выполнение продолжится, и возвращаемой переменной &lt;code&gt;w&lt;/code&gt; будет присвоено значение выполненного промиса.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;await&lt;/code&gt; под капотом
&lt;/h3&gt;

&lt;p&gt;В первую очередь, V8 помечает функцию, как &lt;em&gt;возобновляемую.&lt;/em&gt;  Значит, что она может быть приостановлена и возобновлена (в точках &lt;code&gt;await&lt;/code&gt;). Затем он создает так называемый &lt;code&gt;implicit_promise&lt;/code&gt;, который возвращается, когда вы вызываете асинхронную функцию, и которое в конечном итоге резолвлится до значения, созданного асинхронной функцией.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-under-the-hood.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-under-the-hood.svg" alt="https://v8.dev/_img/fast-async/await-under-the-hood.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Сравнение между асинхронной функцией и тем, во что V8 ее превращает&lt;/p&gt;

&lt;p&gt;Дальше самое интересное: сам &lt;code&gt;await&lt;/code&gt;. В первую очередь, значение, переданное &lt;code&gt;await&lt;/code&gt;, оборачивается в промис. Затем к этому промису добавляются обработчики, которые возобновят выполнение функции, как только промис будет выполнен, и выполнение асинхронной функции приостанавливается, возвращая &lt;code&gt;implicit_promise&lt;/code&gt; в место вызова. Как только &lt;code&gt;promise&lt;/code&gt; будет выполнен, выполнение асинхронной функции продолжится со значением &lt;code&gt;w&lt;/code&gt; из &lt;code&gt;promise&lt;/code&gt;, и &lt;code&gt;implicit_promise&lt;/code&gt; будет зарезолвлен в &lt;code&gt;w&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Короче говоря, основные шаги для &lt;code&gt;await v&lt;/code&gt; следующие:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Обернуть &lt;code&gt;v&lt;/code&gt; — значение, переданное &lt;code&gt;await&lt;/code&gt; — в промис.&lt;/li&gt;
&lt;li&gt;Добавить обработчики для последующего возобновления асинхронной функции.&lt;/li&gt;
&lt;li&gt;Приостановить выполнение асинхронной функции и вернуть &lt;code&gt;implicit_promise&lt;/code&gt; в место вызова.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Давайте шаг за шагом пройдемся по каждой операции. Предположим, что ожидаемое значение уже является промисом, который был выполнен со значением &lt;code&gt;42&lt;/code&gt;. Тогда движок создает новый &lt;code&gt;promise&lt;/code&gt; и зарезолвлит его с тем, что было передано в &lt;code&gt;await&lt;/code&gt;. Этим самым цепочка промисов откладывается до следующего хода, что в спецификации зовется как &lt;code&gt;[PromiseResolveThenableJob](https://tc39.es/ecma262/#sec-promiseresolvethenablejob)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-step-1.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-step-1.svg" alt="https://v8.dev/_img/fast-async/await-step-1.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Затем движок создает так называемый &lt;code&gt;throwaway&lt;/code&gt; промис. Называется он &lt;em&gt;throwaway (одноразовый/на выброс),&lt;/em&gt;  поскольку ни к чему не привязывается и находится только внутри движка. Этот &lt;code&gt;throwaway&lt;/code&gt; промис связывается с &lt;code&gt;promise&lt;/code&gt; вместе с соответствующими обработчиками для возобновления выполнения асинхронной функции. Операция &lt;code&gt;performPromiseThen&lt;/code&gt;, по сути, выражает то, что за кулисами делает метод &lt;code&gt;[Promise.prototype.then()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)&lt;/code&gt;. Наконец, выполнение асинхронной функции возобновляется и возвращает контроль в место ее вызова.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-step-2.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-step-2.svg" alt="https://v8.dev/_img/fast-async/await-step-2.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Выполнение продолжается в месте вызова, и в итоге стэк вызовов очищается. Потом движок JavaScript запускает микротаски: ранее запланированную &lt;code&gt;[PromiseResolveThenableJob](https://tc39.es/ecma262/#sec-promiseresolvethenablejob)&lt;/code&gt;, которая говорит &lt;code&gt;[PromiseReactionJob](https://tc39.es/ecma262/#sec-promisereactionjob)&lt;/code&gt; сцепить  &lt;code&gt;promise&lt;/code&gt; со значением, переданным в &lt;code&gt;await&lt;/code&gt;. Потом движок возвращает очередь микротасок на обработку, поскольку очередь микротасок должна быть очищена перед обработкой основного цикла событий.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-step-3.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-step-3.svg" alt="https://v8.dev/_img/fast-async/await-step-3.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Далее &lt;code&gt;[PromiseReactionJob](https://tc39.es/ecma262/#sec-promisereactionjob)&lt;/code&gt;, который заполняет &lt;code&gt;promise&lt;/code&gt; значением промиса, которого мы ожидаем — &lt;code&gt;42&lt;/code&gt; — планирует реакцию на &lt;code&gt;throwaway&lt;/code&gt; промис. Движок снова возвращает цикл микротасок, который уже содержит финальную микротаску для обработки.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-step-4-final.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-step-4-final.svg" alt="https://v8.dev/_img/fast-async/await-step-4-final.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Теперь второй &lt;code&gt;[PromiseReactionJob](https://tc39.es/ecma262/#sec-promisereactionjob)&lt;/code&gt; передает разрешение промису &lt;code&gt;throwaway&lt;/code&gt; и возобновляет выполнение асинхронной функции, возвращая значение  &lt;code&gt;42&lt;/code&gt; из &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-overhead.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-overhead.svg" alt="https://v8.dev/_img/fast-async/await-overhead.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Суммируя то, что мы выучили, для каждого &lt;code&gt;await&lt;/code&gt; движку приходится создавать &lt;strong&gt;два дополнительных&lt;/strong&gt; промиса (даже если значение по правую сторону уже им является) и требует &lt;strong&gt;минимум три&lt;/strong&gt; тика очереди микротасок. Кто знал, что одно   &lt;code&gt;await&lt;/code&gt; выражение &lt;em&gt;приведёт к такому количеству оверхеда&lt;/em&gt;?!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-code-before.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-code-before.svg" alt="https://v8.dev/_img/fast-async/await-code-before.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Давайте посмотрим, откуда этот оверхед берется. Первая строка ответственна за создание обёртки промиса. Вторая строка немедленно резолвлит эту обёртку с ожидаемым значением &lt;code&gt;v&lt;/code&gt;. Эти две строки ответственны за один дополнительный промис два из трех микротиков. Дороговато, если &lt;code&gt;v&lt;/code&gt; уже является промисом (что является частым случаем, т.к. приложения обычно навешивают &lt;code&gt;await&lt;/code&gt; на промисы). В редком случае, когда разработчику нужно ожидать &lt;code&gt;await&lt;/code&gt;, например, значения &lt;code&gt;42&lt;/code&gt;, движку все равно придется обернуть его в промис.&lt;/p&gt;

&lt;p&gt;Как оказалось, в спецификации уже есть операция &lt;code&gt;[promiseResolve](https://tc39.es/ecma262/#sec-promise-resolve)&lt;/code&gt;, которая будет выполнять обертку только когда нужно:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-code-comparison.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-code-comparison.svg" alt="https://v8.dev/_img/fast-async/await-code-comparison.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Эта операция вернет промис неизменным и обернет только необходимые значения. Таким путем вы избавитесь от одного дополнительного промиса и двух тиков, поскольку чаще всего в &lt;code&gt;await&lt;/code&gt; мы передаем промис. Это поведение &lt;a href="https://v8.dev/blog/v8-release-72#async%2Fawait" rel="noopener noreferrer"&gt;доступно по умолчанию в V8, начиная с версии 7.2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Вот как работает новый и улучшенный &lt;code&gt;await&lt;/code&gt;, шаг за шагом:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-new-step-1.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-new-step-1.svg" alt="https://v8.dev/_img/fast-async/await-new-step-1.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Давайте снова предположим, что ожидаем промис со значением &lt;code&gt;42&lt;/code&gt;. Благодаря магии &lt;code&gt;[promiseResolve](https://tc39.es/ecma262/#sec-promise-resolve)&lt;/code&gt;, &lt;code&gt;promise&lt;/code&gt; теперь просто ссылается на такой же промис &lt;code&gt;v&lt;/code&gt;, потому на этом шаге больше нет других действий. После того движок действует так же, как и раньше: создает промис &lt;code&gt;throwaway&lt;/code&gt;, с помощью &lt;code&gt;[PromiseReactionJob](https://tc39.es/ecma262/#sec-promisereactionjob)&lt;/code&gt; планирует возобновить асинхронную функцию на следующем тике в очереди, приостанавливает выполнение функции и возвращает в место вызова.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-new-step-2.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-new-step-2.svg" alt="https://v8.dev/_img/fast-async/await-new-step-2.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;В итоге все выполнение кода заканчивается и движок запускает микротаски, которые выполняют &lt;code&gt;[PromiseReactionJob](https://tc39.es/ecma262/#sec-promisereactionjob)&lt;/code&gt;, промису  &lt;code&gt;throwaway&lt;/code&gt; передается разрешение, выполнение асинхронной функции возобновляется, и из &lt;code&gt;await&lt;/code&gt; мы получаем &lt;code&gt;42&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-overhead-removed.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-overhead-removed.svg" alt="https://v8.dev/_img/fast-async/await-overhead-removed.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Эта оптимизация позволяет избежать необходимости оборачивать промис, его значение, переданное в &lt;code&gt;await&lt;/code&gt;, уже им является, и вместо &lt;strong&gt;трех&lt;/strong&gt; микротиков мы выполняем только &lt;strong&gt;один&lt;/strong&gt;. Node.js 8 ведет себя также, только это больше не считается багом, а введенной в стандарт оптимизацией!&lt;/p&gt;

&lt;p&gt;Но все равно кажется неправильным, что движку приходится создавать промис &lt;code&gt;throwaway&lt;/code&gt;, пускай он и является полностью внутренним. Как оказалось, &lt;code&gt;throwaway&lt;/code&gt; промис был нужен только для того, чтобы удовлетворить ограничениям внутренней операции &lt;code&gt;performPromiseThen&lt;/code&gt; API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-optimized.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fawait-optimized.svg" alt="https://v8.dev/_img/fast-async/await-optimized.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fnode-10-vs-node-12.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fnode-10-vs-node-12.svg" alt="https://v8.dev/_img/fast-async/node-10-vs-node-12.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Сравнение &lt;code&gt;await&lt;/code&gt; кода до и после оптимизаций&lt;/p&gt;

&lt;p&gt;Сравнивая &lt;code&gt;await&lt;/code&gt; в Node.js 10 с оптимизированным &lt;code&gt;await&lt;/code&gt; версии Node.js 12, видим следующую разницу:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fbenchmark-optimization.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fv8.dev%2F_img%2Ffast-async%2Fbenchmark-optimization.svg" alt="https://v8.dev/_img/fast-async/benchmark-optimization.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Заключение
&lt;/h2&gt;

&lt;p&gt;Мы ускорили асинхронные функции благодаря двум значительным оптимизациям:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;удалением двух дополнительных микротиков&lt;/li&gt;
&lt;li&gt;удалением &lt;code&gt;throwaway&lt;/code&gt; промиса.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;А еще для JavaScript разработчиков у нас есть пара советов по оптимизации:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;предпочитайте &lt;code&gt;async&lt;/code&gt; / &lt;code&gt;await&lt;/code&gt; вручную написанному коду с промисами&lt;/li&gt;
&lt;li&gt;придерживайтесь встроенной реализации промисов, предложенной движком, чтобы использовать преимущества шорткатов, т.е. избегайте двух микротиков для &lt;code&gt;await&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




</description>
    </item>
  </channel>
</rss>
