Pull to refresh
75
-1
Alexey Andreev @konsoletyper

Пользователь

Send message

Это почему-то так не работает. Крупные компании вливают триллионы бабла и получается известная субстанция. Тут работает неизмеримый и неформализуемый творческий ресурс, пассионарность и т.п. Я сам конечно не люблю, когда при мне вот так многозначительно выражаются подобными гуманитарными терминами, но что делать, если факт есть факт :-) Большие и неповоротливые с их миллиардами не могут, а маленькие и независимые — могут

А почему я должен выводить 0 для случая, если ключа нету в мапе? Как я уже ответил другому комментатору, ситуации разные бывают, я просто привёл простейший пример. Могу специально покопаться и поискать места в самом kotlinc, где используются !!, и это вполне уместно.

Я прекрасно знаю, что значит !!.. Иногда?.. не спасает, т.к. для ключа, которого нет в мапе, всё равно нет валидного значения (можно вернуть что-то бессмысленное, но тогда что-то всё равно упадёт в будущем). Ситуации бывают разные, я написал не один десяток тысяч строк на Котлине, и всякого повидал. Мой комментарий ставил целью опровергнуть заявление, что KNPE был брошен где-то в "интеропе с Java". Отнюдь. Котлин не всесилен, и есть куча способов упасть в чистом котлине. Можно даже NPE словить, если пошаманить со статическими инициализаторами или с lateinit.

Есть масса сценариев, где котлиновская null safety не работает. Например, мы вот только что положили в мапу элемент, примерно так:


val map = mutableMapOf<String, String>()
map["foo"] = "bar"

А потом (мамой клянёмся, что "foo" есть в мапе):


println(map["foo"]!!.length)

если не поставить !!, компилятор ругается. К сожалению, null safety не всесилен. Он помогает устранить часть ошибок с NPE, которые возможны в Java, но ото всех ошибок такого рода избавиться невозможно. Справедливости ради стоит так же сказать, что для Java есть статические анализаторы кода и наборы аннотаций, которые позволяют добиться примерно аналогичного поведения (Checker Framework, инспекции IntelliJ IDEA).

А при просто потоке машинных кодов, обращающихся к каким-то произвольным участкам памяти, как гарантированно понять, что какой-то посторонний код (в худшем случае вообще в параллельном потоке — этого сейчас в WASM нет, но планируется в ближайшее время) не поправит данную ячейку памяти (которая потом используется как указатель)?

Таки WASM — это не машинные коды, про него очень много всего можно сказать. Аналогичная проблема в JVM решается с помощью memory model. Собственно, там предполагается, что один поток может не увидеть изменений в памяти, сделанные другим потоком. Причём, на некоторых процессорных архитектурах это прямо аппаратно так (вроде на MIPS было
так).


Ээээ что? Что значит браузеру не нужно с данной памятью работать? Ему не нужна вообще никакая память для своей личной работы или как? А если хоть какая-то нужна, то как её защитить от доступа из WASM модуля с помощью защиты страниц виртуальной памяти?

Ещё раз — браузер мапит где-то для себя страницы. При этом для WASM он резервирует страниц на 4Гб, что никак не противоречит тому, что ещё какие-то страницы памяти выделены под сам WASM. При этом для доступа к WASM-хипу можно использовать что-то вроде такого: wasm_heap[ptr & 0xFFFFFFFF], поэтому мы гарантированно либо попадём в хип, либо получим sigsegv.


Формат wasm — это по сути и есть IR, представляющее собой ассемблерные инструкции некого виртуального процессора

WASM — это очень неудобный IR. Как минимум, потому что не SSA. Держу пари, что WASM машины внутри себя его во что-то более удобное преобразуют.


Причём этот формат является чуть изменённой (упрощённой и с большей безопасностью) версией IR из LLVM

Да вообще достаточно мало общего. Если для вас это "чуть изменённая" версия LLVM-биткода, то можно и JVM-байткод или MSIL назвать "чуть изменённой" версией LLVM.


а это единственное разумное решение

А обоснование какое?

ok, во-первых, WebAssembly выполняется как раз в v8. Во-вторых, эти же лимиты они про то, как работает v8 с точки зрения юзера, а внутри он может что угодно и как угодно выделять. В третьих, зарезервировать в ОС страниц можно сколько угодно, пока в них процесс писать не будет, рельно память выделяться не будет. Впрочем, если резервировать с правильными флагами, то при попытке записи вообще будет генериться sigsegv (ну или как это называется в Windows), и никогда не будет выделяться физическая память. Что я, собственно, и предлагаю.

В смысле? Chrome и не должен никому ничего давать. Это операционка должна давать Chrome-у. Есть какие-то причины, почему она не даст зарезервировать приложению (Chrome) страниц суммарно на 4Gb?

Так нужны гарантии, что эти значения не меняются. Именно в этом проблема, а не в определение самих значений.

Гарантий нет, но ведь всегда есть достаточно безопасные и предсказуемые кусочки программы, про которые что-то можно доказать. Кроме того, повторяю, что для верхнего лимита адресуемой памяти есть как минимум нижняя граница, которая указана просто в модуле. Для оптимизатора не важны точные значения. Для доказательства неравенства Amin < A < Amax, Bmin < B < Bmax, где Amin, Amax, Bmin, Bmax — константы, достаточно доказать, что Amax < Bmin. Если считать, что B — это размер хипа, то Bmin и Bmax тупо указаны в модуле. А Amax можно попытаться каким-то образом оценить, хотя бы сверху. Можно даже не с константами работать, а с инвариантами цикла. Ну например есть код:


while (start < end) {
  *start++ = 0;
}

компилятор вставит проверку


while (start < end) {
  if (start >= wasm_upper_bound) generate_exception();
  *start++ = 0;
}

далее, у нас есть два инварианта цикла: wasm_upper_bound и end. К сожалению, start таковым не является, так что классический loop unswitching сделать не получится. Но известно, что он выполняется неравенство start < end с инвариантом цикла, так что можно попробовать модифицировать loop unswitching, чтобы он делал следующее


if (end < wasm_upper_bound) {
    while (start < end) {
        *start++ = 0;
    }
} else {
    while (start < end) {
        if (start >= wasm_upper_bound) generate_exception();
        *start++ = 0;
    }
}

VirtualProtect это в Винде. Только ничем эти функции не помогут для решения данной проблемы. Если закрыть с помощью них доступ к какой-то области памяти, то каким образом сам браузер будет работать с этой областью памяти?

А браузеру вовсе не нужно с данной памятью работать. Учитывая, что в WebAssembly пока размер кучи ограничен 2^32, а процессоры у нас 64-битные, можно тупо нарезервировать страниц как раз на 2^32, но на реальные физические отобразить ровно столько, сколько заявлено в дескрипторе wasm-модуля (ну и по мере вызова grow_memory отображать дополнительные).


С учётом того, что в LLVM IR имеются векторные типы, это конечно очень большая загадка, как это делается

Вы хотите сказать, что clang берёт и сам генерит векторные инструкции? Мне казалось, что они всё-таки получаются в процессе работы самого LLVM. Учитывая, что с точки зрения меня, как разработчика приложений, формат wasm является всего лишь форматом обмена данными, мне всё равно, в какой там IR браузер разворачивает мой wasm-модуль и как его оптимизировать. Или разработчики WebAssembly не хотят, чтобы браузер делал векторизацию и хотят переложить эту заботу на компилятор? Ну тогда я всё равно не вижу практической невозможности позволить генерацию быстрого кода, я вижу только невозможность сочетать это с быстрым запуском кода.

Каким это образом, если и значение указателя и значения границ определены только в рантайме?

Вот уж не знаю как. Я точно помню, как общался с сотрудниками Мозиллы и они мне говорили про багу в range analysis ещё в альфа-версии WebAssembly. Как минимум, нижняя граница определена в compile-time (в WebAssembly-модуле для heap необходимо указывать минимальный и максимальный размер хипа), а то, что указатель известен только в рантайме, ещё не значит, что его диапазон нельзя оценить с помощью статического анализа. Такое, например, реализовано в JVM для устранения проверки на выход за границу массива.


Каким образом можно в рамках одного процесса закрыть некому коду доступ к какой-то области выделенной памяти?

mprotect в POSIX. В Windows тоже был системный вызов, но я с ходу не вспомнил название.


Нет там ничего необходимого. В будущем появится (соответствующие инструкции), но пока нет.

Там есть всё необходимое. Вот как-то C-компиляторы делают это. А они, заметьте, никогда не оптимизируют непосредственно AST C. Т.е. они строят IR (промежуточное представление), которое как раз очень похоже на портабельный ассемблер, а потом уже находят в нём циклы, которые можно векторизовать. Для этого есть куча способов, есть много специализорованной литературы, которая рассказывает про это (вот даже последние редакции Dragon Book рассказывают про векторизацию), и есть ещё большая куча всяческих статей от авторов тех или иных компиляторов.


С учётом того, что в наиболее требовательных к ресурсам приложениях (программирование различных МК, ядра ОС, дравейров и т.п.) как раз применяется режим C++ с отключёнными исключениями, то данная особенность если и влияет на производительность, то только положительно. )))

Полагаю, что статья автора как раз не про драйвера и ядра ОС.

Но при этом тот же C++ код скомпилированный в машинные коды всегда получается в разы быстрее. И на это есть две фундаментальные причины

Мне кажется, что проверка на выход за границы heap-а в ряде случаев может убираться оптимизатором. Кроме того, часть работы по отлову выходов за границу хипа можно сделать через защиту памяти. Что касается SIMD, опять же, браузер может сам анализировать WASM и автоматом векторизовать его (для этого в WASM есть всё необходимое). По крайней мере, LLVM ровно так и делает. А вот ещё моменты, которые ещё, как мне кажется, могут сильно влиять на производительность:


  1. Нет возможности получить указатель на переменную в стеке. Если такой указатель где-то берётся, то приходится размещать переменную в shadow-стеке, со всеми накладными расходами на поддержание shadow-стека.
  2. Нет возможности вообще как-то погулять по стеку, чтобы, например, сгенерировать исключение. Да и нативную поддержку исключений пока не завезли. Так что единственная возможность — это вставлять всюду проверки.

Честно говоря, ответа ни на один вопрос я не получил.


Какова практическая необходимость такого изменения? Вон, люди в Kotlin наоборот плюются от отсутствия static.

static не исчезает, он становится неотличим от дефолта. Вместо статичных классов и полей — объекты по умолчанию. Внутренние объекты по умолчанию, таким образом, будут как бы статичными только для себя, по отношению к своему внешнему классу, т.е., если внешний класс сам статичный и так до самого внешнего, то — статичные абсолютно.

Во-первых, тут вы оперируете слишком большим количеством терминов, которые мы с вами не согласовали (объекты по-умолчанию, внутренние объекты). Можете более подробно описать эти особенности дизайна вашего языка? Объекты по-умолчанию это что-то вроде object в Kotlin? Т.е. есть сущность "объект" а есть — "класс". Или всё является классом? Тогда каким образом конструируются объекты по-умолчанию для классов, у которых нет конструкторов без параметров. Во-вторых, вопрос был в том, зачем избавляться от static.


Т.е. структурная типизация как в go и typescript вместо существующей номинативной? А зачем?

На что получил что-то про особенности реализации, которые, увы, не зная всех особенностей вашего замысла, понять практически невозможно (предлагаю всё-таки для конструктивного общения описать дизайн языка, с bnf и операционной семантикой, хотя бы в очень нестрогом виде).


А вопрос-то был про то, зачем вам в языке структурная типизация вместо номинативной. В чём вы видите преимущества первой перед второй?


Да, но как тогда вы в методе будете указывать, что переданный параметр должен обладать такими-то свойствами? Для этого нужно будет вводить синтаксис для описания анонимного типа-структуры. А потом и тайпалиасы прибегут (что и будет аналогом интерфейсов).

В языке аргумент это просто примитив либо название метода, который класс по совместительству. Там нет ничего кроме классов, они же — методы с фигурными скобками. Для машины аргумент это ссылка на структуру объекта, в которой 1: vtable, в котором хеши из сигнатур (у нас же только методы) и значение-ссылка на такую же структуру другого объекта; 2: хэштаблица из хешей (2) предков и себя самого; и 3: адрес метода.

Я опять же, спрашивал не столько про особенности реализации, сколько про дизайн языка. Вот у вас, судя по всему, структурная типизация, так? Вот в typescript я могу написать


function foo(x: { bar(x: number): string }) {
    x.bar(23)
}

и сюда подойдёт инстанс любого класса, у которого есть bar. Если попробовать в foo засунуть что-то не обладающее методом bar нужной сигнатуры, то компилятор нарисует ошибку. Вот где в вашем языке определяется сигнатура метода? Как в ней описать требования к передаваемому в метод объекту, если нет интерфейсов?


Ага, т.е. множественное наследование реализаций? А как будет реализовываться vtable?

Не совсем множественное наследование. vtable состоит из key — хешкода сигнатуры метода, и value — ссылки структуру, описанную в (3), где есть и тело метода и такой же vtable и т.д. до победы

А вот это уже был вопрос про реализацию. И правильно я понимаю, что вы предлагаете виртуальный вызов вместо обычной косвенной адресации со смещением (одна инструкция на x86) делать lookup в хэш-таблице? А как же производительность?


Простите, а как при ссылке на overloded-метод должен резолвиться метод с конкретной сигнатурой?

Прямо по хешу сигнатуры метода из vtable. И есть куда оптимизировать простые случаи.

Вопрос снова был про семантику а вы ответили реализацией. Хорошо, попробую вот так. Пусть у нас есть в Java такие объявления:


void foo(Integer x);

void foo(String x);

когда я пишу o.foo(23), компилятор точно знает, что надо вызвать первый метод, потому что он подходит по сигнатуре. Далее, если у нас все методы есть просто поля функционального типа, то такой резолв становится невозможен (например, он невозможен в JavaScript, где ровно такое поведение, и там похожая (но не такая же) штука реализуется уже в рантайме руками). Именно поэтому для ссылок на методы и в Java используется такой синтаксис, который используется (там по сигнатуре метода в целевом SAM-интерфейсе можно сделать резолв нужного метода). Вы предлагаете отказаться от перегрузки по сигнатуре? А если не предлагаете, то как разработчик должен сообщать компилятору, который из методов ему нужен?


Практика показывает, что голова начнёт чесаться при решении задачи каскадной упаковки, а именно где остановиться и как это правило сформулировать.

Просто свои приватные массивы и примитивы, они же лежат по адресу без всяких vtable. Чужие не надо. В пункте 13 даже есть заделка, но для этого и она наверное медленная.

Ещё раз обратите внимание — практика показывает. На Java вполне можно просто в виде библиотеки это сделать. И никто не делает. Точнее, делают в виде наколенных поделок, которые приходится выбрасывать, потому что при усложнении проекта и появления дополнительных требований оказывается, что никакая автоматика не справится.


Изобретать неявные преобразования, как в C++?

Используется обыкновенный класс с абстрактными полями, по тому же самом принципу. Можно его даже подзаполнить, если хочется. Поскольку есть динамические типы, расширяем подходящий объект этим «темлейтом» и абстрактные методы радостно становятся неабстрактными. Просто этот «темплейт» попадает в хештаблицу предков. Т.е., даже никакого механизма специального не надо. Так получается потому, что из языка всё повыкинули.

Вопрос был про варантность generics, т.е. про возможность объявить такое:


void foo(List<? extends C> supplier, List<? super C> consumer);

у шаблонов в C++ с этим проблемы. Ну как проблемы, там это решается средствами, которых и близко нет в Java (и про которые вы так же ничего не сказали)

Никакого труда не составляло бы за всё это время по пунктам перечислить конкретные наивности и побочные проблемы, но их там просто нет или исчезающе мало.

Мой коммент выше читали? Там как раз некоторые наивности разобраны и обозначены некоторые побочные проблемы.

Модификатор «static» из языка убрать, в качестве static — дефолтный экземпляр, доступный по ссылке на типизированный null.

Какова практическая необходимость такого изменения? Вон, люди в Kotlin наоборот плюются от отсутствия static.


Типы классов сравниваются по хеш-коду, который будет сгенерирован именами поля, именами аргументов, типами и возвращаемыми значениями,

Т.е. структурная типизация как в go и typescript вместо существующей номинативной? А зачем?


Таким образом, больше не нужен «interface» как таковой, только «class», «abstract» — лишь инструкция, запрещающая создание экземпляра

Да, но как тогда вы в методе будете указывать, что переданный параметр должен обладать такими-то свойствами? Для этого нужно будет вводить синтаксис для описания анонимного типа-структуры. А потом и тайпалиасы прибегут (что и будет аналогом интерфейсов). Я кстати ещё молчу про перфоманс instanceof/checkast. Ну и да, это не просто языковое изменение, тут JVM придётся выкинуть.


Добавление правил слияния для «extends».

Ага, т.е. множественное наследование реализаций? А как будет реализовываться vtable? Через ужасные трюки как в C++, с нескольми указателями на vtable в каждом инстансе? С неизбежным ростом размера заголовка объекта? А как при этом обеспечивать identity equality?


Динамические типы. Темплейты как в Си, а не как женериксы в Java

И как это будет работать с variance? Изобретать неявные преобразования, как в C++?


Больше не понадобятся специальные ссылки на метод, как в Java 8. Ссылка на метод будет означать ссылку на объект.

Простите, а как при ссылке на overloded-метод должен резолвиться метод с конкретной сигнатурой?


Чтобы прямо хотелось распаковать-использовать-упаковать в одном методе, а не чесалась голова «как это лучше сделать».

Практика показывает, что голова начнёт чесаться при решении задачи каскадной упаковки, а именно где остановиться и как это правило сформулировать.


Массивам нужен контроль границ только во время записи, и я бы дал возможность читать напрямую из памяти без контроля границ массива.

Чтение за границами массива — это в 99% случаев не то, чего мы реально хотели, а значит — ошибка. Одним из принципов дизайна Java является fail fast, что, исходя из моего опыта (и из чужого опыта — тоже), является благом. Проблема производительности решается в данном случае JIT-компилятором, который может сделать range analysis и доказать, что проверка не нужна. Они, конечно, слабоваты эти range-анализы, но на практике если у вас такой сложный код, что в нём сложно что-то доказать, то в нём и проверка на выход за границу массива не будет узким местом.

А можно пример кода, который при компиляции даёт байт-код, в котором потеряна информация, необходимая для статического анализа?

Я так понял, PVS-Studio анализирует исходники. А почему не байт-код, как это делает findbugs? Это позволило бы одним инструментом покрыть много разных JVM-языков. Ведь наверняка PVS-Studio парсит исходники и строит какой-нибудь control flow graph, ищет def-use chains (а то и в SSA всё перегоняет). Всё то же самое можно прекрасно делать с байт-кодом. Или в подобных рассуждениях есть какой-то подвох?

Аллокатор как часть ОС? А зачем? Обычно ОС предоставляют низкоуровневые вызовы, резервирующие память страницами, вроде sbrk/mmap в POSIX или VirtualAlloc в Windows, а уже поверх них в userspace реализуется что-нибудь вроде malloc. Последний может быть и не нужен, если вы запускаете на своей ОС какую-нибудь Java, где свой аллокатор, который непосредственно в тот же mmap бегает.

Обычно хватало GwtQuery. Есть Elemental. Есть форк реакта под GWT.

Всё это весьма и весьма кривое и неудобное. После Angular 2+/TypeScript реально неуклюжим кажется.


Java плохой язык для декларативного программирования и DSL-ей. В нем фактически отсутствует возможность читабельно написать иерархические структуры, такие как DOM.

В JavaScript тоже нет никаких возможностей таких. Вот для этого и прикрутили JSX/TSX.


Как выход либо прикручивается сторонний темплейтер с кучей не type-safe байндингов

Либо прикручивается сторонний темплейтер, поддерживающий типизированные байндинги, вроде этого.


В Java много специфической семантики, отличной от JS. С одной стороны мы хотим сделать по стандарту, как в GWT

А чего в плане интероперабельности не хватает в JSInterop?

Кусок страницы, которая рисуется динамически, не отображается или отображается некорректно или в одном браузере норм, в другом — нет.

Помню, такое бывало в далёком 2008-м. Но в 2018-м? Первый раз слышу. К тому же, опять приведу аналогию с нативным миром — это всё равно, что забиндить к Java через JNI какой-нибудь GTK, и потом пересесть на C, только потому, что приходится в процессе разбираться с устройством этого GTK, который написан на C.


И вот начинаются танцы с браузером, который работает с JS.

Ну так JSNI/JSInterop позволяет взять и писать на JS. Это же небольшое количество нативного кода. Помню, сам в проекте на 200К строк Java-кода понаставил несколько грязных хаков на JSNI (это в далёком 2014-м, когда корпоративные клиенты всё ещё сидели на IE8), но в совокупности их там и на 200 строк не набралось бы.


JS станет не главным языком, на котором все крутится, а «одним из», на равных условиях. Вот тогда ваша аналогия с x86/x64 будет уместна, ну и неизбежно в браузерах появятся средства отладки не привязанные к языку (сейчас что-то есть, но не в том объеме).

Что-то не разделяю я вашего оптимизма. Как разработчики браузеров не сделали нормальных тулов для транспайлеров, так они и для компиляторов в wasm ничего не сделают. Вроде уже года полтора как зарелизили MVP, но до сих пор по дебагу ничего нет, кроме весьма куцых сорсмапов.

На вопрос, чем плох GWT, пусть отвечают создатели GWT, которые его успешно похоронили и теперь делают J2CL. А так же многочисленные пользователи GWT, которые в нём почему-то разочаровались. Лично от себя могу добавить следующие моменты:


  1. Действительно, когда-то GWT имел детские болезни, вроде отсутствия инкрементальной компиляции, кривой дебаг и т.п. Сейчас это полечено.
  2. Морально устаревшая и кривая библиотека виджетов. Это когда весь мир сидит на реакте.
  3. Кривой интероп (который заменили в последних версиях на нормальный).
  4. Невозможность совмещать в одном проекте Java, Scala и Kotlin.

Короче, проблема номер два решалась выпиливанием старой библиотеки виджетов и написанием нормального Angular/Java (ведь написал же гугл форки для TS и Dart, чего бы Java не осилить?), а не выкидыванием всего GWT и заменой его на J2CL

Information

Rating
Does not participate
Location
München, Bayern, Германия
Date of birth
Registered
Activity

Specialization

Specialist
Senior
From 6,000 €
Java
Compilers
Kotlin
Gradle