169 похожих чатов

Народ, а кто-нибудь знает почему в emscripten/clang решили реализовать throw

и try-catch операторы языка С++ через взаимодействие с js-рантаймом ? Это же какое-то дико костыльное и медленное решение когда ошибка выбрасывается на стороне js и потом ловится на стороне js и потом уже вызывается с++ обработчик.
Я знаю что в С++ реализация ошибок в нативном коде происходит через раскрутку стека что невозможно в webassembly так как отсутствует доступ к стеку. Но разве это единственно возможная реализация логики try/catch-trow ?
Почему нельзя было реализовать выброс ошибок через добавление в С++ код логику "кодов ошибок" (способ который принято использовать в языке Си) ? Механизм примерно следующий - когда в С++ коде компилятор видит оператор throw то он добавляет код который запишет в глобальную переменную значение ошибки и вернет управление из функции а на стороне вызывающей функции добавит код который проверяет что глобальная переменная не пустая чтобы в случае чего также вернет управление и это будет продолжаться до тех пор пока в вызываемой функции не окажется try-catch оператор где и будет выполнен код который находится в catch-операторе
В итоге у нас вся обработка ошибок не будет покидать wasm-рантайм и будет работать достаточно эффективно (не zero overhead в случае С++ когда ошибка не выбрасывается но и не хуже чем подход с "кодами ошибок" в языке Си который используется во многих кодовых базах как на си так и на с++)
И совершенно непонятно почему в Emscripten (и в wasm-бэкенде компилятора clang) не реализовали компиляцию throw-try-catch в очевидное и логическое решение через "коды ошибок" (раз в wasm отсутствует доступ к стеку) а вместо этого наворотили костыльное решение через js-рантайм ?

20 ответов

20 просмотров
Богдан- Автор вопроса

Так я и упомянул в сообщении что нет доступа к стеку но это не оправдание так как логику trow-try-catch можно было бы реализовать через компиляцию в "коды ошибок" и никакое взаимодействие в js-рантаймом не нужно. Почему они так не сделали?

И прощай раздельная компиляция! 😃

Богдан- Автор вопроса
Alexander Chichigin
И прощай раздельная компиляция! 😃

Почему? Компиляция throw-try-catch в "коды ошибок" приведет к тому что поменяется abi - то есть функция будет возвращать не тип T а некий юнион "T | Error" - как это помешает раздельной компиляции ?

Богдан
Почему? Компиляция throw-try-catch в "коды ошибок"...

Философия C++, это должен делать программист а не компилятор.

Богдан
Почему? Компиляция throw-try-catch в "коды ошибок"...

Во-первых, как должен возвращаться код ошибки из функции int foo(int a, int b, int c) { return a*b + c; } ? Во-вторых, вот есть у нас библиотека или даже просто файл на чистом C, который никаких исключений, понятное дело, не бросает. А коды ошибок свои возвращает, кстати. И что теперь, его всё равно компилировать под обработку исключений, потому что ну а вдруг? Тормоза всем даром чтобы никто не ушёл обиженным?

Богдан- Автор вопроса
Alexander Chichigin
Во-первых, как должен возвращаться код ошибки из ф...

А в принципе не нужно ничего выдумывать на уровне языка С++ или Си - то во что будет происходить компиляция конструкций trow/try-catch относится к ABI а не к хедерам/интерфейсам/сигнатурам типов при раздельной компиляции. На уровне abi просто будет соглашение о том что реализация исключений будет происходить через "коды ошибок" а не через раскрутку стека И у webassembly соотвественно будет свой abi так (как там отсутствует доступ к стеку). И поскольку любой возврат функции в wasm это один из i32/i64/f32/f64 типов - то соотвественно для того чтобы отличить ошибку от значения нужно выделить специальную глобальную переменную ErrorCode и проверять ее на нуль и если не равно то также вернуть управление из функции (local.set $temp (call $someFunc)) (if i32.eqz(global.get $ErrorCode) (return 0) ) И на всем пути от выброса ошибки через thow до ее поимки конструкцией try-catch в wasm-коде будет добавлены эти вкрапления кода В случае же раздельной компиляции С/C++ кода компилятор не может знать (видя ее сигнатуру функции в хедере) будет ли она прямо или косвенно выбрасывать ошибку и тогда компилято должен считать что функция выбрасывает ошибку и будет компилировать с добавлением кода проверки при ее вызове. А чтобы код в райнтайме не выполнялся медленно из-за лишних проверок компилятор может при компиляции в объектный файл добавить метаинформацию о том какие функции выбрасывают ошибки и уже при линковке удалить из бинарника эти лишние вкрапления кода с проверкой на ошибку для функций который не выбрасывают (прямо или косвенно) ошибки. Вот что из того что я описал неправильно? Почему такое до сих пор не реализовали в emscripten/clang ?

Богдан- Автор вопроса

Наоборот, наличие оператора trow и try-catch и тот факт что во многих местах стандартной библиотеки используется как раз выброс ошибок через throw говорит о том что философия C++ больше про выброс ошибок через trow а не про ручную манипуляцию с "кодами ошибок".

Богдан
Наоборот, наличие оператора trow и try-catch и тот...

Открой книжки по C++. Исключения для исключительных ситуаций.

Dmitry M
Открой книжки по C++. Исключения для исключительны...

исключения это просто механизм. причем относительно бесплатный, если их не вызывать

Богдан- Автор вопроса

Да, будут вкрапления кода на всем call site, и что? Во-первых оверхед будет мизерным из-за бранч-предикторов в современных цпу а во-вторых как по вашему пишутся проекты на С++ где в стайлгайдах (например Google C++) запрещено использовать trow-try-catch ? В гугловской кодовой базе с++ если функция возвращает ошибку то ее точно также нужно проверять на каждой итерации цикла только делая это руками в коде.

А если не возвращает, то не нужно. В Вашем же варианте придётся проверять все функции, потому что они могут "перевыбросить" исключение из какой-то вызываемой функции.

Богдан- Автор вопроса
Alexander Chichigin
А если не возвращает, то не нужно. В Вашем же вари...

Прошу читать мои сообщения внимательней) Я написал про вкрапления этого проверочное кода во "все места где вызывается функция" только для тех функций для которых есть только сигнатура и ничего не известно про тело функции. И это нужно только в случае если кому-то нужна раздельная компиляция и этот оверхед будет только на этапе компиляции в объектный файл а дальше при линковке нескольких объетный файлов в один исполняемый все эти лишние вкрапления кода будут удалены за счет дополнительных метаданных (записанные в каждый объектный файл) о том какие функции не выбрасывают (прямо или косвенно) исключения. То есть в рантайме лишних проверок на ошибки не будет и код будет работать так же быстро как если писался с использованием google c++ стайлгайда

Богдан
Прошу читать мои сообщения внимательней) Я написал...

> какие функции не выбрасывают (прямо или косвенно) исключения Чтобы узнать вот это вот "или косвенно" Вам потребуется whole program analysis. Вы думали, C++ долго компилируется? Попробуйте с WPA! 😏

Богдан
Да, будут вкрапления кода на всем call site, и что...

result / expect монады не бесплатны, конечно это быстрее чем исключения, но передавать их для каждого call site который потенциально может вернуть ошибку это ужасная идея. Именно поэтому в Rust, Ocaml, Haskell etc ты должен это делать явно руками.

Богдан- Автор вопроса

В этом примере используется юнион/тюпл (или что та у раста), когда результат врапится в струтуру дальше записывается в память и достается из памяти. Естественно это будет тормозить, уверен результаты будут другие если ошибка будет пробрасываться через глобальную переменную (global.set и проверяться в global.get) без лишнего врапа в структуру и работой с памятью При этом никто не говорит что исключения бесплатны. Просто есть трейд-офф в реализации - раскрутка стека дает zero overhead в случае если ошибки не выбрасываются но когда они выбрсываются получаем очень сильное падение производительности. В случае же подхода "кодов ошибок" такой сильной деградации (в случае выброса ошибок) не будет но при этом будет небольшое замедление если ошибки не выбрасываются И вот это небольшое замедление кода (если ошибка не выбрасывается) должно быть очень незначительным и едва заметным (если это не так значит это недостатки реализации) потому что это всего лишь одна дополнительная ассембленая инструкция с проверкой регистра и прыжком. И механизмы предсказывания ветвлений в процессорах будут убирать на нет этот оверхед

Глобальные переменные не оптимизируются в работу с регистром -- они в регистры читаются из памяти, а потом записываются обратно. Тем более между вызовами функций. 🤦‍♀

Богдан
В этом примере используется юнион/тюпл (или что та...

В таком случае не получиться все это делать с рекурсивными функциями и многопоточностью. Так или иначе нужен стек. Что для туплов что для исключений. В wasm нет доступа с стеку. Для Result как я показал выше он эмулируется

Богдан- Автор вопроса
MaxGraey
В таком случае не получиться все это делать с реку...

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

Богдан- Автор вопроса
Alexander Chichigin
Глобальные переменные не оптимизируются в работу с...

Я бы так не утверждал - и в llvm и в turbofan есть куча оптимизаций и там даже работа с памятью может перемещаться в регистры (когда компилятор может за счет flow analysis доказать strict aliasing переменных т.е что переменные указывают на разные участки памяти) то глобальные переменные и подавно должны оптимизироваться в работу с регистрами. Возможно это сейчас работает пока в случае инлайна функций но сами оптимизации по идее могут работать и между функцями. Есть даже специальная оптимизация (не помню термин) когда функция в случае если она не инлайнится то она все равно анализируется и то как она использует локальные переменные учитывается при распределении регистров во всех местах где эта функция вызывается (что-то про caller vs callee save registers)

Похожие вопросы

Обсуждают сегодня

@MrMiscipitlick А можешь макрос написать, который будет вычислять смещение относительно переданных меток? Просто .label1-.label2, и вернуть значение.
КТ315
35
я не магистр хаскеля, но разве не может лейзи тип конвертнуться в не-лейзи запросив вычисление содержимого прям при инициализации?
deadgnom32 λ madao
100
А еще в перле можно уже @arr1 + @arr2?
Sergei Zhmylove
53
Подобного рода ;Следующие три строки это директивы ассемблера, ;которые можно не задавать, т.к.работаем в Visual Studio. ;Символ ";" - это начало однострочного комментария ...
Егор Анелькин
3
Кто-нибудь знает почему SPM клонирует репо целиком? Некоторые репы просто огромные, как та же swift-syntax которая нужна для использования макросов. Сначала подумал, что это...
iMike
6
Привет всем. появился вопрос. Разрабатываю сайт, в данный момент он запущен. Хостинг beget. Добавляю на сайт яндекс метрику с помощью полей client-settings (взято отсюда http...
Andrew
2
Подскажите, где смотреть результат выполнения программы? Код: ;.686 ;Система команд процессора 686 ;.MODEL FLAT,stdcall ;Модель памяти плоская, станда...
Егор Анелькин
5
еще вопрос, допустим мы создадим char массив из 10 элементов и присвоим ему через сканф 10 символов. и выведем все символы. Хотел спросить последний элемент /0 будет включать...
Anthem
11
открыть папку в проводнике: 1 - ShellExecute 2 - ExecuteProcess 3 - OpenDocument что лучше выбрать?
Alexey Kulakov
12
;.686 ;Система команд процессора 686 ;.MODEL FLAT,stdcall ;Модель памяти плоская, стандартный ;вызов процедуры ;option casemap:no...
Егор Анелькин
1
Карта сайта