Design: call_indirect против абстракции

Созданный на 7 мая 2020  ·  36Комментарии  ·  Источник: WebAssembly/design

call_indirect - очень полезная функция для WebAssembly. Однако эффективность и хорошее поведение инструкции неявно зависели от простоты системы типов wasm. В частности, каждое значение wasm имеет ровно один (статический) тип, которому оно принадлежит. Это свойство позволяет избежать ряда известных проблем с вызовами нетипизированных функций в типизированных языках. Но теперь, когда wasm выходит за рамки числовых типов, он дошел до того момента, когда нам нужно понимать эти проблемы и помнить о них.

call_indirect принципиально работает, сравнивая ожидаемую подпись вызывающего абонента с определенной подписью вызываемого. Имея только числовые типы, WebAssembly обладал тем свойством, что эти сигнатуры были равны тогда и только тогда, когда при прямом вызове функции, на которую ссылается соответствующий funcref , проверялся тип. Но есть две причины, которым скоро не суждено сбыться:

  1. С подтипом прямой вызов будет работать до тех пор, пока фактическая определенная сигнатура функции является «под-сигнатурой» ожидаемой сигнатуры, то есть все входные типы являются подтипами типов параметров функции, а все выходные типы являются супертипами функций типы результатов. Это означает, что проверка равенства между ожидаемой сигнатурой косвенного вызова и определенной сигнатурой функции приведет к возникновению ряда совершенно безопасных ситуаций, которые могут быть проблематичными для поддержки языков с интенсивным использованием подтипов и косвенных вызовов (как было поднят во время обсуждения откладывание подтипов). Это также означает, что если модуль намеренно экспортирует функцию с более слабой сигнатурой, чем функция была определена с помощью, то call_indirect можно использовать для доступа к функции с ее частной определенной сигнатурой, а не только с ее более слабой публичной сигнатурой ( проблема, которая была только что обнаружена и поэтому еще не обсуждалась).
  2. При импорте типа модуль может экспортировать тип без экспорта определения этого типа, обеспечивая абстракцию, на которую системы, подобные WASI, планируют во многом полагаться. Эта абстракция предотвращает зависимость других модулей от ее конкретного определения во время компиляции . Но во время выполнения абстрактный экспортируемый тип просто заменяется его определением. Это важно, например, для правильной работы call_indirect с экспортируемыми функциями, экспортированные сигнатуры которых ссылаются на этот экспортируемый тип. Однако, если вредоносный модуль знает определение этого экспортируемого типа, он может использовать call_indirect для преобразования между экспортируемым типом и его предполагаемым секретным определением, потому что call_indirect сравнивает подписи только во время выполнения, когда эти два типа действительно одинаковы . Таким образом, вредоносный модуль может использовать call_indirect для доступа к секретам, которые должны быть абстрагированы экспортируемым типом, и может использовать call_indirect для подделки значений экспортируемого типа, которые могут нарушать важные для безопасности инварианты, не захваченные в определение самого типа.

В обеих вышеупомянутых ситуациях call_indirect можно использовать для обхода абстракции экспортируемой подписи модуля. Как я уже упоминал, до сих пор это не было проблемой, потому что wasm имел только числовые типы. И изначально я подумал, что, отложив выделение подтипов, все опасения, касающиеся call_indirect также были устранены. Но недавно я понял, что, удалив подтипы, «новый» тип (названный externref в https://github.com/WebAssembly/reference-types/pull/87) фактически заменяет для импорта абстрактного типа. Если люди хотят, чтобы это было на самом деле, то, к сожалению, нам нужно принять во внимание вышеупомянутое взаимодействие между call_indirect и импортом типов.

Теперь есть много потенциальных способов решения вышеуказанных проблем с помощью call_indirect , но у каждого есть свои компромиссы, и это просто слишком большое пространство для дизайна, чтобы можно было быстро принять решение. Поэтому я не предлагаю решить эту проблему здесь и сейчас. Скорее, решение, которое необходимо принять в данный момент, заключается в том, нужно ли выиграть время для правильного решения проблемы в отношении externref . В частности, если мы на данный момент ограничим call_indirect и func.ref только проверкой типа, когда связанная подпись полностью числовая, тогда мы будем обслуживать все основные варианты использования косвенных вызовов и в в то же время оставьте место для всех возможных решений вышеуказанных проблем. Однако я не знаю, практично ли это ограничение как с точки зрения усилий по реализации, так и с точки зрения того, препятствует ли оно приложениям externref , которых люди ждут. Альтернативный вариант - оставить call_indirect и func.ref как есть. Вполне возможно, что это означает, что, в зависимости от решения, к которому мы приходим, externref может не быть инстанцируемым, как импорт истинного типа, и / или что externref может (по иронии судьбы) не иметь возможность иметь любые супертипы (например, может не быть подтипом anyref если мы в конечном итоге решим добавить anyref ).

Я, от своего имени, считаю оба варианта приемлемыми. Хотя у меня есть предпочтения, я не настаиваю на принятии того или иного решения, и я считаю, что у вас будет лучший доступ к информации, необходимой для принятия хорошо обоснованного решения. Я просто хотел, чтобы вы все знали, что необходимо принять решение, и в то же время я хотел установить осведомленность о всеобъемлющей проблеме с помощью call_indirect . Если вы хотите получить более подробное объяснение этой проблемы, чем приведенное выше резюме, прочтите следующее.

call_indirect против абстракции, в деталях

Я буду использовать обозначение call_indirect[ti*->to*](func, args) , где [ti*] -> [to*] - это ожидаемая сигнатура функции, func - это просто funcref (а не таблица funcref и индекс) и args - это значения to* передаваемые в функцию. Точно так же я буду использовать call($foo, args) для прямого вызова функции с индексом $foo передачей аргументов args .

Теперь предположим, что $foo - это индекс функции с объявленными типами ввода ti* и типами вывода to* . Вы можете ожидать, что call_indirect[ti*->to*](ref.func($foo), args) эквивалентно call($foo, args) . Действительно, это так прямо сейчас. Но неясно, сможем ли мы поддерживать такое поведение.

call_indirect и подтип

Один пример потенциальной проблемы возник при обсуждении подтипов. Предположим следующее:

  • tsub - это подтип tsuper
  • экземпляр модуля IA экспортирует функцию $fsub которая была определена с типом [] -> [tsub]
  • модуль MB импортирует функцию $fsuper с типом [] -> [tsuper]
  • Экземпляр модуля IB представляет собой экземпляр модуля MB, созданный с помощью $fsub IA в виде $fsuper (что разумно сделать - даже если сейчас это невозможно, эта проблема связана с потенциальными предстоящими проблемами)

Теперь подумайте, что должно произойти, если IB выполнит call_indirect[ -> tsuper](ref.func($fsuper)) . Вот два наиболее вероятных исхода:

  1. Вызов завершается успешно, поскольку ожидаемая сигнатура и заданная сигнатура совместимы.
  2. Вызов ловушки, потому что две сигнатуры различны.

Если бы мы выбрали результат 1, знайте, что нам, вероятно, потребуется использовать один из двух методов, чтобы сделать это возможным:

  1. Для импортированных функций необходимо сравнить call_indirect с сигнатурой импорта, а не с сигнатурой определения.
  2. Выполните хотя бы линейную проверку во время выполнения на совместимость подтипов ожидаемой подписи и сигнатуры определения.

Если вы предпочитаете метод 1, знайте, что он не будет работать, как только мы добавим ссылки на типизированные функции (с подтипами вариантов). То есть func.ref($fsub) будет ref ([] -> [tsub]) а также ref ([] -> [tsuper]) , и все же метода 1 будет недостаточно, чтобы уберечь call_indirect[ -> super](ref.func($fsub)) от треппинга. Это означает, что для результата 1, вероятно, потребуется метод 2, который влияет на производительность.

Итак, давайте подробнее рассмотрим результат 2. Методика реализации здесь , чтобы проверить , если ожидаемая сигнатуру call_indirect в IB равно сигнатуру определения $fsub в IA. На первый взгляд может показаться, что основным недостатком этого метода является то, что он перехватывает ряд вызовов, которые можно безопасно выполнить. Однако другой недостаток заключается в том, что это потенциально приводит к утечке безопасности для IA.

Чтобы увидеть, как это происходит, давайте немного изменим наш пример и предположим, что, хотя экземпляр IA внутренне определяет $fsub как имеющий тип [] -> [tsub] , экземпляр IA экспортирует его только с типом [] -> [tsuper] . Используя метод для результата 2, экземпляр IB может (злонамеренно) выполнить call_indirect[ -> tsub]($fsuper) и вызов будет успешным. То есть IB может использовать call_indirect чтобы обойти сужение IA для сигнатуры его функции. В лучшем случае это означает, что IB зависит от аспекта IA, который не гарантируется подписью IA. В худшем случае IB может использовать это для доступа к внутреннему состоянию, которое IA могло намеренно скрывать.

call_indirect и импорт типов

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

  • Экземпляр модуля IC определяет тип capability и экспортирует этот тип, но не его определение, как $handle
  • Экземпляр модуля IC экспортирует функцию $do_stuff которая была определена с типом [capability] -> [] но экспортирована с типом [$handle] -> []
  • модуль MD импортирует тип $extern и функцию $run с типом [$extern] -> []
  • ID экземпляра модуля - это MD модуля, созданный с экспортированным IA $handle как $extern и с экспортированным IA $do_stuff как $run

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

Теперь предположим, что идентификатору экземпляра удалось получить значение e типа $extern и выполнить call_indirect[$extern -> ](ref.func($run), e) . Вот два наиболее вероятных исхода:

  1. Вызов завершается успешно, поскольку ожидаемая сигнатура и заданная сигнатура совместимы.
  2. Вызов ловушки, потому что две сигнатуры различны.

Результат 2 делает call_indirect практически бесполезным с импортированными типами. Итак, для результата 1 следует понимать, что тип ввода $extern не является определенным типом ввода $do_stuff (который вместо этого равен capability ), поэтому нам, вероятно, потребуется использовать один из два метода преодоления этого разрыва:

  1. Для импортированных функций сравнивайте call_indirect с сигнатурой импорта, а не с сигнатурой определения.
  2. Помните, что во время выполнения тип $extern в идентификаторе экземпляра представляет capability .

Если вы предпочитаете метод 1, поймите, что он снова не будет работать, как только мы добавим ссылки на типизированные функции. (Основная причина та же, что и с подтипами, но для иллюстрации аналога здесь потребуется еще больше текста.)

Остается техника 2. К сожалению, это еще раз представляет потенциальную проблему безопасности. Чтобы понять, почему, предположим, что ID является вредоносным и хочет получить содержимое $handle которое IC хранила в секрете. Предположим далее, что ID имеет хорошее предположение относительно того, что $handle самом деле представляет capability . ID может определять функцию идентификации $id_capability типа [capability] -> [capability] . Учитывая значение e типа $extern , ID может затем выполнить call_indirect[$extern -> capability](ref.func($id_capability), e) . Используя метод 2, этот косвенный вызов будет успешным, потому что $extern представляет capability во время выполнения, а ID вернет необработанный capability который представляет e . Точно так же, учитывая значение c типа capability , ID может выполнить call_indirect[capability -> $extern](ref.func($id_capability), c) чтобы преобразовать c в $extern .

Заключение

Надеюсь, я прояснил, что call_indirect имеет ряд значительных предстоящих проблем с производительностью, семантикой и / или безопасностью / абстракцией - проблемами, которых WebAssembly удалось избежать до сих пор. К сожалению, из-за того, что call_indirect является частью ядра WebAssembly, эти проблемы пересекают ряд предложений, находящихся в стадии разработки. На данный момент я думаю, что было бы лучше сосредоточиться на наиболее актуальном таком предложении, ссылочных типах, где нам нужно решить, ограничивать ли call_indirect и func.ref только числовыми типами для сейчас - ограничение, которое мы могли бы ослабить в зависимости от того, как мы в конечном итоге решим общие проблемы с помощью call_indirect .

(Извините за длинный пост. Я изо всех сил старался объяснить сложное взаимодействие кросс-модульных функций типирования во время компиляции и во время выполнения, и продемонстрировать важность этих взаимодействий как можно более кратко.)

Самый полезный комментарий

С другой стороны, вы продемонстрировали, что castable anyref можно использовать для обхода статических механизмов абстракции.

Абстракции статического типа недостаточно в языке с динамическим приведением типов. Потому что статическая абстракция полагается на параметричность, а приведение ее нарушает. Ничего нового в этом нет, об этом писали. В таком контексте необходимы другие механизмы абстракции.

Попытка обойти это ограничение на использование абстрактных типов приводит к поражению их цели. Рассмотрим вариант использования WASI. Не имеет значения, реализован ли модуль WASI и любой тип, который он экспортирует, хостом или в Wasm. Если вы произвольно ограничиваете определяемые пользователем абстрактные типы, реализация Wasm перестает быть взаимозаменяемой с реализацией хоста в целом.

  1. Это не помогает сделать call_indirect уважение подтипов (что, я думаю, вы уже сказали явно)

Хм? Это часть правил выделения подтипов, как и по определению.

  1. Это не препятствует использованию call_indirect для использования экспортируемой функции с ее определенной подписью, а не с ее экспортируемой подписью.

Я не говорил, что это так. Я сказал, что это проблема не самого call_indirect, а вопроса выбора подходящего механизма абстракции типов для языка с приведениями.

Кроме того, нет веских причин, по которым компиляция OCaml (или любого подобного языка) должна требовать введения вариантных типов. Даже если бы это могло быть немного быстрее в теории (что, я сомневаюсь, будет иметь место в движках текущего поколения, скорее наоборот), вариантные типы представляют собой серьезное осложнение, которое не должно быть необходимым для MVP. Я не совсем разделяю ваш аппетит к преждевременному усложнению. ;)

Повторное равенство функций: существуют языки, такие как Haskell или SML, которые не поддерживают это, поэтому могут получить прямую выгоду от func refs. OCaml выбрасывает структурное равенство и явно имеет определяемое реализацией поведение для физического. Остается открытым, позволяет ли это всегда возвращать false или бросать для функций, но на практике этого может быть достаточно, и его стоит изучить, прежде чем переходить к дорогостоящей дополнительной упаковке.

[В качестве мета-комментария, я был бы очень признателен, если бы вы смягчили свои лекции и, возможно, рассмотрели идею о том, что это мир, в котором, возможно, набор компетентных людей не является одиночным и что следы мозгов иногда применялись раньше.]

Все 36 Комментарий

Спасибо за это подробное описание, Росс! У меня небольшой вопрос: в разделе " call_indirect и импорт типов" вы пишете,

Если вы предпочитаете метод 1, поймите, что он снова не будет работать, как только мы добавим ссылки на типизированные функции.

Это также связано с предостережением из предыдущего раздела о том, что проблема возникает только после того, как мы добавим вариантное подтипирование к типизированным ссылкам на функции?

Нет. Все проблемы в разделе подтипов не зависят от импорта типов, а все проблемы в разделе импорта типов не зависят от подтипов. Что касается конкретной проблемы, о которой вы спрашиваете, учтите, что значение типа ref ([] -> [capability]) может быть возвращено экспортированной функцией как значение типа ref ([] -> [$handle]) , которое затем может быть преобразовано в funcref и косвенно вызывается. В отличие от экспортированной функции, это изменение перспективы значения происходит во время выполнения, а не во время связывания, поэтому мы не можем решить его, сравнивая с сигнатурой импорта, поскольку ссылка на функцию никогда не импортировалась сама.

module instance IC defines a type capability and exports the type but not its definition as $handle
Как это будет работать? Должно быть что-то, что соединяет capability и $handle чтобы IC знала, как с этим бороться?
Также на основе https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md#exports импортированные типы полностью абстрактны. Так что даже если $capability экспортируется, это абстрактно. Возможно, я что-то неправильно понимаю.

Аналогичный вопрос для экспорта для module instance IC exports a function $do_stuff that was defined with type [capability] -> [] but exported with type [$handle] -> [] .

Я могу представить себе какое-то отношение подтипов, используемое для этого, например, если $capability <: $handle , тогда мы можем export $capability as $handle . Но в начале этого раздела было упомянуто, чтобы отложить подтипы в сторону, поэтому я отложил это в сторону ... Но я также подумал об этом немного больше:
Если: $capability <: $handle , мы можем export $capability as $handle , но export ([$capability] -> []) as ([$handle] -> []) должен "потерпеть неудачу", потому что функции контравариантны в аргументе.

При экспорте типов модуль указывает подпись, например type $handle; func $do_stuff_export : [$handle] -> [] , а затем создает экземпляр подписи, например type $handle := capability; func $do_stuff_export := $do_stuff . (Полностью игнорируйте конкретный синтаксис.) Средство проверки типов затем проверяет, «учитывая, что $handle представляет capability в этом модуле, является ли экспорт func $do_stuff_export := $do_stuff действительным в этом модуле?». Поскольку тип $do_stuff равен [capability] -> [] , его подпись точно совпадает с подписью $do_stuff_export после создания экземпляра $handle с помощью capability , поэтому проверка прошла успешно. (Здесь нет подтипов, просто подстановка переменных.)

Однако обратите внимание, что сама подпись ничего не говорит о $handle . Это означает, что все остальные должны рассматривать $handle как абстрактный тип. То есть подпись намеренно абстрагирует детали реализации модуля, и все остальные должны уважать эту абстракцию. Цель этой проблемы - показать, что call_indirect можно использовать для обхода этой абстракции.

Надеюсь, это немного проясняет проблему!

Спасибо, это проясняет ситуацию. У меня вопрос по разделу подтипов (извините, что прыгнул):

Я следую сценарию, в котором мы хотим, чтобы IB успешно выполнила call_indirect[ -> tsuper](ref.func($fsuper)) , имея call_indirect «сравнить с сигнатурой импорта, а не с сигнатурой определения».

И вы добавили, что (из-за типизированных ссылок на функции) нам также нужны

  1. Выполните хотя бы линейную проверку во время выполнения на совместимость подтипов ожидаемой подписи и сигнатуры определения.

Должна ли быть совместимость между «ожидаемой подписью и импортируемой подписью »? Поскольку мы предполагаем, что мы сделали call_indirect для сравнения сигнатуры импорта с ожидаемой сигнатурой.

Если проверяется совместимость между ожидаемым и импортируемым, то позже call_indirect[ -> tsub]($fsuper) должно завершиться ошибкой.

Методы 1 и 2 представлены как два ортогональных способа заставить этот косвенный вызов работать. К сожалению, метод 1 несовместим с типизированными ссылками на функции, а метод 2, вероятно, слишком дорог. Так что, похоже, ни один из них не сработает. Таким образом, в оставшейся части раздела рассматривается, что произойдет, если мы не воспользуемся ни одним из них, а просто будем придерживаться простого сравнения на равенство между ожидаемой и определенной сигнатурой. Извините за путаницу; Отсутствие запланированной семантики означает, что я должен обсудить три потенциальные семантики.

Будьте осторожны, чтобы не делать слишком много выводов. ;)

Я предполагаю, что call_indirect должен оставаться таким же быстрым, как сегодня, и поэтому всегда требуется только проверка эквивалентности типов, независимо от того, сколько подтипов мы добавляем в язык. В то же время проверка во время выполнения должна быть согласована со статической системой типов, т. Е. Должна учитывать отношение подтипов.

Теперь эти, казалось бы, противоречивые требования могут быть довольно легко согласованы, если мы уверены, что типы, используемые с call_indirect , всегда находятся на концах иерархии подтипов.

Один из установленных способов обеспечения этого - ввести понятие точных типов в систему типов. Точный тип не имеет подтипов, только супертипы, и у нас будет (exact T) <: T .

При этом мы можем потребовать, чтобы целевой тип в call_indirect был точным типом. Более того, тип самих функций, естественно, уже является точным типом этой функции.

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

Это все, что нужно для гарантии того, что текущая методика реализации простого сравнения указателей на канонизированных типах функций остается в силе. Это не зависит от того, какие еще есть подтипы или насколько необычно мы делаем подтипы функций. (FWIW, я обсуждал это с Люком некоторое время назад и планировал создать PR, но он был заблокирован из-за незавершенных изменений в истории подтипов и в то предложение, к которому сейчас переходит.)

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

Пара замечаний:

Альтернативный вариант - оставить call_indirect и func.ref как есть.

AFAICS, невозможно запретить ref.func для функций, которые включают ссылочные типы. Это серьезно повредило бы многие варианты использования, то есть все, что связано с первоклассными функциями, работающими на externref (обратные вызовы, перехватчики и т. Д.).

Вполне возможно, что это означает, что в зависимости от решения, к которому мы приходим, externref может не быть инстанцируемым, как импорт истинного типа, и / или что externref может (по иронии судьбы) не иметь никаких супертипов (например, может не быть подтипом anyref, если мы в конце концов решим добавить anyref).

Вы можете уточнить? Я не вижу связи.

Будьте осторожны, чтобы не делать слишком много выводов. ;)

Я не уверен, к какому выводу вы имеете в виду. Мое заявленное заключение состоит в том, что есть ряд проблем с call_indirect о которых нам нужно знать и которые следует планировать. Кажется, вы предполагаете, что эти проблемы несущественны, потому что у вас есть решение. Но это решение не было рассмотрено или принято CG, и мы не должны планировать его, пока оно не будет сделано. Я специально просил не обсуждать решения, потому что для их оценки и сравнения потребуется время, и есть решения, которые нам нужно принять, прежде чем у нас будет время для того, чтобы должным образом провести эти оценки и сравнения. Но для того, чтобы у людей не сложилось впечатление, что эта проблема решена, и, следовательно, избежать неотложного решения, я потрачу секунду, чтобы быстро обсудить ваше решение.

Один из установленных способов обеспечения этого - ввести понятие точных типов в систему типов.

Точные типы вряд ли можно считать устоявшимся решением. Во всяком случае, точные типы выявили проблемы, над решением которых их сторонники все еще работают. Интересно, что вот тема, в которой команда TypeScript изначально увидела, как точные типы предлагаемой вами формы могут решить некоторые проблемы, но затем они в конечном итоге [реализовались) (https://github.com/microsoft/TypeScript/issues/12936 # issuecomment-284590083), что точные типы создают больше проблем, чем решают. (Примечание для контекста: это обсуждение было вызвано точными типами объектов Flow, которые на самом деле не являются формой точного типа (в теоретическом смысле), а вместо этого просто запрещают объектный аналог префиксного подтипа.) Я мог представить, как мы воспроизводим этот поток здесь.

В качестве примера того, как такого рода проблемы могут возникать в WebAssembly, предположим, что мы не отложили выделение подтипов. Тип ref.null будет exact nullref с использованием точных типов. Но exact nullref не будет подтипом exact anyref . Фактически, согласно обычной семантике точных типов, вероятно, никакие значения не будут принадлежать exact anyref потому что, скорее всего, тип времени выполнения значения не будет точно anyref . Это сделало бы call_indirect полностью непригодным для использования за anyref s.

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

Вы можете уточнить? Я не вижу связи.

Вы имеете в виду длинное предложение. Какую часть вы бы хотели, чтобы я более подробно остановился? Можно предположить, что вы могли упустить общую проблему с call_indirect и импортом типов. Ваше предложение точных типов решает только проблемы с подтипами, но мы установили выше, что call_indirect имеет проблемы даже без подтипов.

Это серьезно повредило бы многие варианты использования, то есть все, что связано с первоклассными функциями, работающими на externref (обратные вызовы, перехватчики и т. Д.).

Да, это то, о чем я надеялся получить больше информации. Насколько я понимаю, основной вариант использования call_indirect - поддержка указателей функций C / C ++ и виртуальных методов C ++. Насколько я понимаю, этот вариант использования в настоящее время ограничен числовыми подписями. Я знаю о других потенциальных применениях call_indirect , но, как я уже упоминал, я предлагал временное ограничение, поэтому важно то, каково текущее использование call_indirect . Учитывая, что call_indirect прежнему требует таблицы и индекса, а не просто funcref , он не кажется особенно хорошо спроектированным для поддержки обратных вызовов. Я не знал, было ли это, потому что в настоящее время он не используется для этой цели.

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

@RossTate :

Во всяком случае, точные типы выявили проблемы, над решением которых их сторонники все еще работают. Интересно, что вот тема, в которой команда TypeScript первоначально увидела, как точные типы предлагаемой вами формы могут решить некоторые проблемы, но затем они в конце концов поняли, что точные типы создают больше проблем, чем решают. (Примечание для контекста: это обсуждение было вызвано точными типами объектов Flow, которые на самом деле не являются формой точного типа (в теоретическом смысле), а вместо этого просто запрещают объектный аналог префиксного подтипа.) Я мог представить, как мы воспроизводим этот поток здесь.

Круглые скобки здесь являются ключевыми. Я не уверен, что именно они имеют в виду в этой беседе, но, похоже, это не одно и то же. В противном случае такие утверждения, как «предполагается, что тип T & U всегда присваивается T , но это не удается, если T является точным типом», не имели бы смысла (это не t не выполняется, потому что T & U будет недопустимым или нижним). Остальные вопросы касаются в основном прагматики, то есть того, где программист захочет их использовать (для объектов), что в нашем случае неприменимо.

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

В качестве примера того, как такого рода проблемы могут возникать в WebAssembly, предположим, что мы не отложили выделение подтипов. Тип ref.null будет точным nullref с использованием точных типов. Но точный nullref не будет подтипом точного anyref.

Здесь нет разногласий. Отсутствие подтипов - цель точных типов.

Фактически, в соответствии с обычной семантикой точных типов, скорее всего, никакие значения не будут принадлежать точному anyref, потому что, скорее всего, тип времени выполнения значения не будет точно anyref.

Правильно, комбинация (exact anyref) не является полезным типом, учитывая, что единственная цель anyref - быть супертипом. Но почему это проблема?

Это сделало бы call_indirect полностью непригодным для любых ссылок.

Вы уверены, что теперь не путаете уровни? Тип функции (exact (func ... -> anyref)) очень полезен. Он просто несовместим с типом, скажем, (func ... -> (ref $T)) . То есть exact предотвращает нетривиальное выделение подтипов для типов функций. Но в том-то и дело!

Может быть, вы путаете (exact (func ... -> anyref)) с (func ... -> exact anyref) ? Это несвязанные типы.

Ваше предложение точных типов решает только проблемы с подтипами, но мы установили выше, что call_indirect имеет проблемы даже без подтипов.

Вы каким-то образом предполагаете, что сможете экспортировать тип без его определения как средство определения абстрактного типа данных. Ясно, что этот подход не работает при наличии динамического приведения типов (call_indirect или иначе). Вот почему я все время говорю, что нам понадобится абстракция типа в стиле newtype, а не в стиле ML.

Насколько я понимаю, основной вариант использования call_indirect - поддержка указателей функций C / C ++.

Да, но это не единственный вариант использования ref.func , о котором я говорил, потому что вы включили его в предложенное вами ограничение (может быть, без необходимости?). В частности, будет call_ref , что не требует проверки типа.

Вы каким-то образом предполагаете, что сможете экспортировать тип без его определения как средство определения абстрактного типа данных. Ясно, что этот подход не работает при наличии динамического приведения типов (call_indirect или иначе). Вот почему я все время говорю, что нам понадобится абстракция типа в стиле newtype, а не в стиле ML.

Итак, похоже, вы согласны с тем, что точные типы ничего не делают для решения проблемы с call_indirect и импортом типа. Но вы также говорите, что нет смысла решать эту проблему, потому что она в любом случае будет проблемой из-за приведений во время выполнения. Есть простой способ предотвратить эту проблему: не позволять людям выполнять приведение во время выполнения к абстрактным типам (если абстрактный тип явно не говорит, что он может быть приведен). В конце концов, это непрозрачный тип, поэтому мы не можем предположить, что он демонстрирует структуру, необходимую для выполнения приведения. Таким образом, даже если существует вероятность того, что точные типы решают проблему выделения подтипов, преждевременно игнорировать вторую половину проблемы.

Как я уже сказал, у каждого решения есть компромиссы. Вы, кажется, предполагаете, что ваше решение имеет только те компромиссы, которые вы сами определили, и, похоже, предполагаете, что CG предпочтет ваше решение другим. У меня тоже есть потенциальное решение этой проблемы. Он гарантирует постоянные проверки, основан на технологии, уже используемой в виртуальных машинах, решает все проблемы (я считаю), не требует добавления каких-либо новых типов и фактически добавляет дополнительные функции в WebAssembly с известными приложениями. Однако я не предполагаю, что это работает так, как я ожидал, и что я не упустил из виду некоторые недостатки, потому что у вас и других не было возможности просмотреть это. Я также не предполагаю, что CG предпочтет компромиссы альтернативным вариантам. Вместо этого я пытаюсь выяснить, что мы можем сделать, чтобы дать нам время проанализировать варианты, чтобы группа экспертов, а не только я, могла быть тем, кто принимает обоснованное решение по этой сквозной теме.

В частности, будет call_ref , который не требует проверки типа.

Ключевое слово в вашем предложении - воля . Я прекрасно понимаю , что есть приложения call_indirect с нечисловыми типами , что люди хотят , чтобы поддержали. И я ожидаю , что мы придумали конструкцию , которая поддерживает эту функциональность и адреса вопросы выше. Но, как я уже сказал, в идеале у нас может быть время на разработку этого дизайна, чтобы мы не выпускали быстро функцию с сквозными последствиями, прежде чем у нас будет возможность исследовать эти последствия. Итак, мой вопрос: есть ли основные программы, которым сейчас нужна эта функциональность. Если есть, нет необходимости строить предположения; просто укажите на некоторые из них и проиллюстрируйте, как они в настоящее время полагаются на эту функциональность.

Вы каким-то образом предполагаете, что сможете экспортировать тип без его определения как средство определения абстрактного типа данных. Ясно, что этот подход не работает при наличии динамического приведения типов (call_indirect или иначе). Вот почему я все время говорю, что нам понадобится абстракция типа в стиле newtype, а не в стиле ML.

Мне это кажется фундаментальной проблемой. Является ли обеспечение конфиденциальности определений экспортируемых типов целью предложения по импорту типов? Из этой беседы я понял, что @rossberg считает, что в настоящее время это не цель. Давайте обсудим и согласуем этот вопрос, прежде чем обсуждать решения, чтобы все мы могли работать с одним и тем же набором предположений.

@RossTate :

Итак, похоже, вы согласны с тем, что точные типы ничего не делают для решения проблемы с call_indirect и импортом типов.

Да, если под этим вы имеете в виду вопрос о том, как добавить функцию для определения абстрактных типов данных. Существует несколько способов последовательной работы абстракции типов, но такая функция будет в дальнейшем.

Ключевое слово в вашем предложении - воля. Я полностью осознаю, что существуют приложения call_indirect с нечисловыми типами, которые люди захотят поддерживать.

Инструкция call_ref находится в предложении ссылки на функцию, поэтому довольно близко, в любом случае, перед любым потенциальным механизмом абстрактного типа данных. Вы предлагаете приостановить его до тех пор?

@tlively :

Является ли обеспечение конфиденциальности определений экспортируемых типов целью предложения по импорту типов? Из этой беседы я понял, что @rossberg считает, что в настоящее время это не цель.

Это цель, но механизм абстрактных типов данных - это отдельная функция. И такой механизм должен быть спроектирован таким образом, чтобы он не влиял на дизайн импорта. Если бы это было так, то мы поступили бы очень неправильно - абстракция должна быть обеспечена на сайте определения, а не на сайте использования. К счастью, это не ракетостроение, и пространство для проектирования достаточно хорошо изучено.

Спасибо, @rossberg , в этом есть смысл. Добавление примитивов абстракции в последующее предложение после импорта и экспорта типов звучит для меня нормально, но было бы здорово, если бы мы могли записать детали того, как мы планируем это сделать в ближайшее время. Дизайн импорта и экспорта типов ограничивает и информирует дизайн импорта и экспорта абстрактных типов, поэтому важно иметь хорошее представление о том, как абстракция будет работать в будущем, прежде чем мы завершим первоначальный дизайн.

В дополнение к подробному описанию этого плана, поскольку эта проблема с call_indirect демонстрирует, что она влияет на неотложные решения, можете ли вы объяснить, почему вы, похоже, отклоняете мое предложение о том, что абстрактные типы не должны быть преобразованы (если явно не ограничены для преобразования )? Они непрозрачны, поэтому кажется, что это предложение соответствует общепринятой практике абстрактных типов.

@tlively , да, согласился. Плюс разные другие вещи, о которых я хотел написать некоторое время. Сделаю, когда я проработаю все последствия # 69 . ;)

@RossTate , потому что это сделало бы абстрактные типы данных несовместимыми с приведениями. Просто потому, что я хочу, чтобы другие не видели _through_ абстрактный тип, я не обязательно хочу, чтобы они (или я) не преобразовывали _to_ абстрактный тип. Создание такой ложной дихотомии нарушит основные варианты использования приведений. Например, конечно, я хочу иметь возможность передавать значение абстрактного типа в полиморфную функцию.

@rossberg Не могли бы вы прояснить, что это за основной вариант использования, который вы имеете в виду? Мое лучшее предположение относительно интерпретации вашего примера тривиально разрешимо, но, возможно, вы имеете в виду что-то другое.

@RossTate , рассмотрим полиморфные функции. За исключением Wasm-generics, при их компиляции с использованием приведений вверх / вниз из anyref должна быть возможность использовать их со значениями абстрактного типа, как и любые другие, без дополнительной обертки в другие объекты. Обычно вы хотите иметь возможность обрабатывать значения абстрактного типа, как и любые другие.

Хорошо, давайте рассмотрим полиморфные функции, и предположим, что импортированный тип - это Handle :

  1. В Java есть полиморфные функции. Его полиморфные функции ожидают, что все (ссылка на Java) значения будут объектами. В частности, у них должен быть v-образный стол. Модуль Java, использующий Handle, скорее всего, будет указывать Java-класс CHandle возможно, реализующий интерфейсы. Экземпляры этого класса будут иметь член (уровня wasm) типа Handle и v-таблицу, которая предоставляет указатели на функции для реализации различных классов и методов интерфейса. При передаче полиморфной функции на уровне поверхности, которая на уровне wasm является просто функцией для объектов, модуль может использовать тот же механизм, который он использует для преобразования в другие классы для преобразования в CHandle .
  2. OCaml имеет полиморфные функции. Его полиморфные функции ожидают, что все значения OCaml будут поддерживать физическое равенство. Поскольку wasm не может рассуждать о безопасности типов OCaml, его полиморфные функции, вероятно, также должны будут интенсивно использовать приведение типов. Специализированная структура литья, вероятно, сделает это более эффективным. По любой из этих причин модуль OCaml, скорее всего, укажет алгебраический тип данных или тип записи THandle который соответствует этим нормам и имеет член (уровня wasm) типа Handle . Его полиморфные функции затем будут приводить значения OCaml к THandle как они это делали бы с любым другим алгебраическим типом данных или типом записи.

Другими словами, поскольку модули полагаются на нормы представления значений поверхностного уровня для реализации таких вещей, как полиморфные функции, а абстрактные импортированные типы, такие как Handle, не удовлетворяют этим нормам, перенос значений неизбежен. По этой же причине одно из исходных приложений для anyref было заменено типами интерфейса. И мы разработали тематические исследования, демонстрирующие, что anyref не требуется и даже не подходит для поддержки полиморфных функций.

С другой стороны, вы продемонстрировали, что приведение anyref можно использовать для обхода статических механизмов абстракции. Упомянутый вами план механизма абстракции - это попытка исправить эту проблему с помощью механизмов динамической абстракции. Но есть ряд проблем с механизмами динамической абстракции. Например, нельзя экспортировать ваш тип i31ref как абстрактный тип Handle без риска того, что другие модули будут использовать anyref и преобразовать его в дескрипторы (например, в возможности). Вместо этого нужно перепрыгивать через дополнительные обручи и накладные расходы, которые были бы ненужными, если бы вместо этого мы просто обеспечили стандартную статическую абстракцию.

Кроме того, теперь, когда (я думаю) я лучше понимаю, как вы собираетесь использовать точные типы, я понимаю, что ваше намерение не решает ни одну из двух основных проблем, на которые я обратил внимание с помощью call_indirect и подтипов:

  1. Это не помогает сделать call_indirect подтипами (что, я думаю, вы уже сказали явно)
  2. Это не препятствует использованию call_indirect для использования экспортируемой функции с ее определенной подписью, а не с ее экспортируемой подписью.

Так что это нетривиальная проблема. Вот почему, учитывая нехватку времени, я бы предпочел сосредоточиться на оценке того, как дать нам время решить эту проблему должным образом. Я не думаю, что необходимо сначала обсудить, стоит ли anyref отбрасывать статическую абстракцию. Я надеялся избежать такого большого обсуждения, чтобы не откладывать дальше.

С другой стороны, вы продемонстрировали, что castable anyref можно использовать для обхода статических механизмов абстракции.

Абстракции статического типа недостаточно в языке с динамическим приведением типов. Потому что статическая абстракция полагается на параметричность, а приведение ее нарушает. Ничего нового в этом нет, об этом писали. В таком контексте необходимы другие механизмы абстракции.

Попытка обойти это ограничение на использование абстрактных типов приводит к поражению их цели. Рассмотрим вариант использования WASI. Не имеет значения, реализован ли модуль WASI и любой тип, который он экспортирует, хостом или в Wasm. Если вы произвольно ограничиваете определяемые пользователем абстрактные типы, реализация Wasm перестает быть взаимозаменяемой с реализацией хоста в целом.

  1. Это не помогает сделать call_indirect уважение подтипов (что, я думаю, вы уже сказали явно)

Хм? Это часть правил выделения подтипов, как и по определению.

  1. Это не препятствует использованию call_indirect для использования экспортируемой функции с ее определенной подписью, а не с ее экспортируемой подписью.

Я не говорил, что это так. Я сказал, что это проблема не самого call_indirect, а вопроса выбора подходящего механизма абстракции типов для языка с приведениями.

Кроме того, нет веских причин, по которым компиляция OCaml (или любого подобного языка) должна требовать введения вариантных типов. Даже если бы это могло быть немного быстрее в теории (что, я сомневаюсь, будет иметь место в движках текущего поколения, скорее наоборот), вариантные типы представляют собой серьезное осложнение, которое не должно быть необходимым для MVP. Я не совсем разделяю ваш аппетит к преждевременному усложнению. ;)

Повторное равенство функций: существуют языки, такие как Haskell или SML, которые не поддерживают это, поэтому могут получить прямую выгоду от func refs. OCaml выбрасывает структурное равенство и явно имеет определяемое реализацией поведение для физического. Остается открытым, позволяет ли это всегда возвращать false или бросать для функций, но на практике этого может быть достаточно, и его стоит изучить, прежде чем переходить к дорогостоящей дополнительной упаковке.

[В качестве мета-комментария, я был бы очень признателен, если бы вы смягчили свои лекции и, возможно, рассмотрели идею о том, что это мир, в котором, возможно, набор компетентных людей не является одиночным и что следы мозгов иногда применялись раньше.]

В качестве мета-комментария, я был бы очень признателен, если бы вы смягчили свои лекции

Слышал.

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

Мой совет основан на консультации с несколькими экспертами.

Абстракции статического типа недостаточно в языке с динамическим приведением типов. Потому что статическая абстракция полагается на параметричность, а приведение ее нарушает. Ничего нового в этом нет, об этом писали. В таком контексте необходимы другие механизмы абстракции.

Среди экспертов, с которыми я консультировался, есть авторы некоторых из указанных статей.

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

Предположим, у меня есть полиморфная функция f(...). В моем типизированном языке есть (подтипные) подтипы и явное приведение типов. Однако приведение типа от t1 к t2 проверяет, является ли t2 подтипом t1. Предположим, переменные типа, такие как X, по умолчанию не имеют подтипов или супертипов (кроме самих себя, конечно). Ожидаете ли вы, что f будет относительно параметрической по отношению к X?

Вот их ответ:

Да, я думаю, это будет параметрическим, поскольку единственная возможность, которую это дает вам, - это писать приведение типов в X, которые эквивалентны функции идентичности, которая уже является параметрически в реляционном отношении.

Это соответствует моему совету. Конечно, это упрощение проблемы, но мы приложили усилия, чтобы исследовать проблему более конкретно для WebAssembly, и до сих пор наше исследование показало, что это ожидание сохраняется даже в масштабе WebAssembly. кроме call_indirect , отсюда и эта проблема.

Обратите внимание, что теоремы, о которых вы говорите, применимы к языкам, в которых все значения допускают приведение. Это наблюдение - вот откуда у нас возникла идея ограничить способность к отливке.

Рассмотрим вариант использования WASI.

Я не понимаю ваших заявлений. Мы рассмотрели вариант использования WASI. К нам я включаю нескольких экспертов по безопасности и даже по безопасности, основанной на возможностях.

В качестве мета-комментария, я был бы очень признателен за то, что мне не нужно обращаться к властям или в CG, чтобы мои предложения были услышаны. Я предположил, что ограничение приведений позволит обеспечить статическую параметричность даже при наличии слепков. Вы немедленно проигнорировали это предложение, обратившись к предыдущим документам, чтобы оправдать это увольнение. Тем не менее, когда я предложил то же самое предложение автору этих статей, они сразу же пришли к такому же выводу, что и я, и как вы могли бы сделать. Перед этим я предположил, что оценка потенциальных решений будет долгим процессом. Вы проигнорировали это предложение, настаивая на том, что вы (сами по себе) решили проблему, и втянули нас обоих в этот долгий разговор. Чрезвычайно трудно добиться прогресса и не расстроиться, когда чьи-то предложения постоянно отклоняются так небрежно. (Я должен пояснить, что я не пытаюсь отклонить ваше предложение как возможное решение здесь; я пытаюсь продемонстрировать, что это не единственное решение и поэтому его следует оценивать вместе с другими.)

Я думаю, что разработка и тщательное изучение детального проекта, направленного на устранение проблем, поднятых в этом вопросе, является важным и своевременным: я на самом деле не думаю, что абстрактные типы следует рассматривать как нечто более отдаленное; Они сейчас нужны WASI.

У меня также есть надежда, что exact + newtype может решить эту проблему, но я согласен с тем, что мы не можем просто ставить ферму на эту догадку в данный момент времени, преждевременно принявшись за дизайн, когда мы (в ближайшее время) отправляем ссылочные типы. Нам нужно время, чтобы хорошенько обсудить это.

При этом я не вижу опасности в разрешении externref в подписях call_indirect в предложении ссылочных типов. Да, если модуль экспортирует значение externref (как глобальное значение const или возвращает его из функции ...), мы не выяснили, можем ли мы снизить значение этого externref . Но call_indirect не снижает externref ; он понижает значение funcref , а externref не отличается от роли i32 относительно проверки на равенство типа funcref. Таким образом, при отсутствии импорта типов, экспорта типов и выделения подтипов в call_indirect я не понимаю, как мы выбираем новый дизайн, который мы еще не приняли в MVP. .

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

Конечно. Я думаю, это хорошая идея - проверить, есть ли опасность или нет.

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

В настоящее время externref является единственным доступным абстрактным типом, поэтому хост на основе WASI будет создавать экземпляр externref с помощью i31ref (или любых других «дескрипторов» WASI). Но я понимаю, что WASI хочет как можно больше перенести свою реализацию в WebAssembly, чтобы уменьшить количество кода, зависящего от хоста. Чтобы облегчить это, в какой-то момент системы WASI могут захотеть обработать externref же, как импорт любого другого типа, и создать его экземпляр с помощью абстрактного экспортированного типа Handle WASI. Но если Handle равно i31ref , то вышеупомянутая реализация call_indirect необходимая для работы через границы модуля, также может использоваться, чтобы люди могли подделывать дескрипторы через externref .

Итак, один из моих вопросов, который, как я теперь заметил, не был четко сформулирован в моем исходном сообщении, заключается в том, хотят ли люди, чтобы externref был инстанцируемым, как и импорт других абстрактных типов?

Итак, один из моих вопросов, который, как я сейчас заметил, не был четко сформулирован в моем исходном сообщении, заключается в том, хотят ли люди, чтобы externref был инстанцируемым, как и импорт других абстрактных типов?

Спасибо, что явно подняли этот вопрос. FWIW, я никогда не понимал, что externref можно создать изнутри модуля WebAssembly. Это подразумевает участие хоста в виртуализации, если WASI хочет использовать externref качестве дескрипторов, но мне это кажется нормальным или, по крайней мере, отдельным обсуждением.

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

С точки зрения модуля wasm, externref не означает ссылку на хост. Это просто непрозрачный тип, о котором модуль ничего не знает. Скорее, это соглашения вокруг externref которые интерпретируют его как ссылку на хост. Например, соглашения модуля, использующего externref для взаимодействия с DOM, будут очевидны в функциях, включающих externref которые импортирует модуль, например parentNode : [externref] -> [externref] и childNode : [externref, i32] -> [externref] . Среда модуля, такая как сам хост, на самом деле дает интерпретацию externref как ссылки на хост, и предоставляет реализации импортированных методов, которые подтверждают эту интерпретацию.

Однако среда модуля не обязательно должна быть хостом, и externref не обязательно должны быть ссылками на хост. Среда может быть другим модулем, который предоставляет функциональные возможности для некоторых типов, которые выглядят как ссылки на хосты, демонстрирующие ожидаемые соглашения. Предположим, что модуль E - это среда модуля M, и этот модуль M импортирует parentNode и childNode как указано выше. Допустим, E хочет использовать модуль M, но хочет ограничить доступ M к DOM, скажем, потому, что E имеет ограниченное доверие M или потому, что E хочет связать любые ошибки, которые могут иметь M, и знает, что потребности M не должны превышать эти ограничения. Что может сделать E, так это создать экземпляр M с «MonitoredRef» как externref M. Предположим, что, в частности, E хочет предоставить M узлов DOM, но гарантировать, что M не поднимается выше по дереву DOM. Тогда MonitoredRef E может быть конкретно ref (struct externref externref) , где второй externref (с точки зрения E) - это узел DOM, на котором работает M, но первый externref является предком тот узел, мимо которого M не может проходить. Затем E может создать экземпляр parentNode M, чтобы он ошибался, если эти две ссылки совпадают. Сам E будет импортировать свои собственные функции parentNode и childNode , что делает E эффективным средством отслеживания взаимодействий с DOM во время выполнения.

Надеюсь, это было достаточно конкретным, чтобы нарисовать правильную картину, но не слишком конкретным, чтобы теряться в деталях. Очевидно, что существует ряд подобных шаблонов. Итак, я предполагаю, что другой способ сформулировать вопрос: хотим ли мы, чтобы externref всегда представлял только точные ссылки на хост?

Единственная часть, которая кажется мне сомнительной, - это «что может сделать E - это создать экземпляр M с« MonitoredRef »как externref M». У меня не сложилось впечатление, что есть планы, чтобы абстрагирование отображалось как externref в других модулях. Насколько я понимаю, externref вообще не является инструментом абстракции.

Я тоже не знаю таких планов; Я просто не знаю, рассматривал ли кто-нибудь такой вариант. То есть должен ли externref быть «примитивным» типом, например, как i32 , или «экземпляром» типа, например, как импортируемые типы?

В моем первоначальном сообщении я указал, что любой способ возможен. Компромисс «примитивной» интерпретации заключается в том, что externref существенно менее полезен / компонован, чем импортированные типы, поскольку последние будут поддерживать варианты использования externref а также вышеприведенные шаблоны. Таким образом, «примитивный» externref , вероятно, станет рудиментарным - существующим только для обратной совместимости. Но вряд ли это будет особенно проблематично, просто неприятно. Самая большая проблема, которую я вижу, заключается в том, что, точно так же, как хорошее поведение call_indirect для числовых типов работает, потому что у них нет супертипов, хорошее поведение call_indirect может зависеть от externref тоже без супертипов.

Ах, ха, да, это объясняет разницу в понимании: я согласен с @tlively, что externref вовсе не является абстрактным и нет понятия «создание экземпляра externref с типом», и я думаю, мы можем быть уверены в этом в будущем. (Поскольку externref является примитивным типом, в отличие от явно объявленного параметра типа, неясно, как можно даже попытаться создать его экземпляр для каждого модуля.)

В отсутствие отрицательных значений этот факт делает wasm почти бесполезным для реализации / виртуализации API-интерфейсов WASI, поэтому план для WASI заключался в переходе от i32 дескрипторов напрямую к импорту типов (и почему я подал импорт типов / # 6 , б / ц нам нужно еще немного).

Поскольку externref является примитивным типом, в отличие от явно объявленного параметра типа, неясно, как можно даже попытаться создать его экземпляр для каждого модуля.

Когда мы добавляем импорт типов, мы можем обрабатывать модули без импорта типов, но с externref как имеющие import type externref наверху. Все будет так же проверять типы, потому что, в отличие от других примитивных типов, externref не имеет связанных примитивных операций (кроме значения по умолчанию). Но с этим неявным импортом теперь мы можем делать такие вещи, как виртуализация, песочница и мониторинг во время выполнения.

Но прежде чем возвращаться к этому вопросу, я думаю, было бы полезно оценить, все ли мы в чем-то на одной странице. Сообщите мне, согласны или не согласны со следующим утверждением и почему: «После того, как импорт типов стал доступным, у модулей нет причин использовать externref и их можно будет использовать повторно / компоновать, если вместо этого используется импорт типа».

Сообщите мне, согласны или не согласны со следующим утверждением и почему: «После того, как импорт типов доступен, у модулей нет причин использовать externref и их можно будет использовать повторно / компоновать, если вместо этого используется импорт типа».

Я согласен с этим утверждением в абстракции. На практике я думаю, что externref останется обычным явлением в веб-контекстах для ссылки на внешние объекты JS, потому что он не требует дополнительной настройки во время создания экземпляра. Но это всего лишь прогноз, и я бы не стал возражать, если я окажусь неправ, и все в конце концов переключатся на использование импорта типов. Ценность externref в том, что мы можем получить его раньше, чем иметь более богатые механизмы, такие как импорт типов. Я бы предпочел сохранить externref простым и увидеть, как он выйдет из употребления, чем неловко превратить его в нечто более мощное позже, когда появятся более элегантные альтернативы.

@tlively ,

FWIW, я никогда не понимал, что externref может быть создан изнутри модуля WebAssembly.

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

@lukewagner , я бы

@RossTate :

Среди экспертов, с которыми я консультировался, есть авторы некоторых из указанных статей.

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

Вот что я спросил:

Предположим, у меня есть полиморфная функция f (...). В моем типизированном языке есть (подтипные) подтипы и явное приведение типов. Однако приведение типа от t1 к t2 проверяет, является ли t2 подтипом t1. Предположим, переменные типа, такие как X, по умолчанию не имеют подтипов или супертипов (кроме самих себя, конечно). Ожидаете ли вы, что f будет относительно параметрической по отношению к X?

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

Когда я сказал, что «абстракции статического типа недостаточно», я не имел в виду, что это невозможно _технически_ (конечно, возможно), но что это _практически_ неадекватно. На практике вам не нужно раздвоение между абстракцией типа и подтипами / литьем (или между параметрическими и непараметрическими типами), потому что это искусственно нарушит композицию на основе приведений.

Я не понимаю ваших заявлений.

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

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

Я думаю, это то, к чему сводятся некоторые из наших разногласий: должен ли MVP поддерживать объединения ссылочных типов или он должен требовать введения и кодирования с явными вариантными типами. К лучшему или худшему, объединения - естественное совпадение с интерфейсом кучи типичных движков, и сегодня их легко и дешево поддерживать. Вариантов не так много, это гораздо более исследовательский подход, который, вероятно, приведет к дополнительным накладным расходам и менее предсказуемой производительности, по крайней мере, в существующих двигателях. И я говорю, что как человек, занимающийся системами типов, в других обстоятельствах, например, в языках программирования, ориентированных на пользователя, предпочитает варианты объединениям. ;)

В качестве мета-комментария, я был бы очень признателен за то, что мне не нужно обращаться к властям или в CG, чтобы мои предложения были услышаны.

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

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

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

На практике вам не нужно раздвоение между абстракцией типа и подтипами / литьем (или между параметрическими и непараметрическими типами), потому что это искусственно нарушит композицию на основе приведений.

Это мнение, а не факт (что делает нас совершенно разумным, чтобы с ними не согласиться). Я бы сказал, что не существует отраслевых типизированных языков ассемблера для многоязычных систем, не зависящих от языка, и поэтому невозможно утверждать о практике. Это то, что заслуживает тщательного (отдельного) обсуждения. Для этого обсуждения вам будет полезно сначала предоставить некоторые подробные тематические исследования, чтобы CG могла сравнить компромиссы.

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

WebAssembly / offer-type-imports # 4, WebAssembly / scheme-type-imports # 6 и WebAssembly / scheme-type-imports # 7, по сути, просили дать больше подробностей по этому плану. Последнее из них решает проблему с GC, но WebAssembly / gc # 86 указывает, что текущее предложение GC на самом деле не поддерживает механизмы динамической абстракции.

На мета-уровне нас попросили отложить это обсуждение и сосредоточиться на обсуждаемой теме. Я нашел ответ @tlively на мой вопрос очень полезным. На самом деле мне очень интересно узнать ваше мнение по этому вопросу.

@RossTate :

Я нашел ответ @tlively на мой вопрос очень полезным. На самом деле мне очень интересно узнать ваше мнение по этому вопросу.

Хм, я думал, что уже прокомментировал это выше . Или вы что-то другое имеете в виду?

Неа. Я подумал, что этот комментарий мог подразумевать согласие с его ответом, но сначала я хотел подтвердить. Спасибо!

@lukewagner , что ты

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

Я хотел бы принять предложение @rossberg о расширении объема предложения по импорту типов, чтобы оно охватывало способность wasm реализовывать абстрактные типы. Как только мы сможем это решить, я думаю, это разблокирует дальнейшее обсуждение ссылок на функции и подтипов.

Потрясающие. Тогда мы все находимся на одной странице (и я тоже думаю, что @tlively дает хорошее резюме вовлеченных компромиссов и обоснование решения).

Таким образом, externref не будет инстанциироваться, и ожидается, что модули, которым нужна дополнительная гибкость при импорте типов, будут преобразованы в импорт типов, когда функция будет выпущена. Мне приходит в голову, что для того, чтобы этот переход был плавным, нам, вероятно, потребуется сделать так, чтобы (некоторые?) Типы импорта создавались с помощью externref по умолчанию, если создание экземпляра не предусмотрено.

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

Между тем, хотя мы рассмотрели насущный вопрос о externref , что делать в целом с call_indirect все еще не решено, хотя и ведется полезное обсуждение того, как это можно решить, поэтому я Я все равно оставлю вопрос открытым.

Спасибо!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

bobOnGitHub picture bobOnGitHub  ·  6Комментарии

arunetm picture arunetm  ·  7Комментарии

nikhedonia picture nikhedonia  ·  7Комментарии

spidoche picture spidoche  ·  4Комментарии

jfbastien picture jfbastien  ·  6Комментарии