WebAssembly & Сложности SIMD-арифметики
Кaк некогда работающий непосредственно с низким уровнем (Си/Ассемблер) и знакомый с технологией SIMD, попытался я средствами JavaScript реализовать некоторые из инструкций прямо в браузере, начав разрабатывать некоторую оболочку (ссылка). Однако, споткнулся на вопросе производительности.
Если при открытии ссылки подождать несколько секунд и кликнуть пунтк меню «View» и отметить галочкой опцию «Pseudo Code», можно увидеть некий сгенерированный листинг, который будет завёрнут в «new Function("_", Pseudo_Code)»… Мною ожидалось, что если вместо многократных циклов с «switch-case» и эмуляцией исполнения инструкций процессора всё перенести внутрь «new Function», то браузер сообразит, что нужно произвести хотя бы минимальную компиляцию кода… Однако, если мышкой порисовать несколько линий на Canvas'е окошка «Graphics Display», то можно заметить жуткое подвисание браузера на момент исполнения той «Функции» с текстом из окошка «Pseudo Code»… Очевидно, нужно всё оптимизировать и переносить на уровень WebAssembly! То есть, как можно видеть из «Pseudo Code», выбросить все getter'ы и setter'ы в первую очередь! А без них всё усложняется на порядки, так как если глянуть на реализацию одной операции сложения… BigInt.prototype.PADD = function(n, mask) { // Packed Add return (((this & mask) + (n & mask))) ^ ((this ^ n) & ~mask); }…котор ая проще операции вычитания… BigInt.prototype.PSUB = function(n, mask) { // Packed Subtract return (((this | ~mask) - (n & mask)) & mask) | ((this - n) & ~mask); }…стан овится очевиднее факт, что без прототипов и прочего сахара то самый «Pseudo Code» не будет уже таким изящным, как на данном этапе реализации оболочки…:yes: Ясно, что WebAssembly не создавался для эстетики и служит своеобразным промежутком между JavaScript и псевдо-кодом, годным для трансляции на язык ассемблера конкретного процессора. Но меня в первую очередь интересует вопрос: Неужели так всё плохо с производительностью getter'ов и setter'ов? Нельзя ли частично ускорить код, поместив «"use asm"» внутрь многочисленных getter'ов и setter'ов?:nono: Например, вот так: BigInt.prototype.PADD = function(n, mask) { var _this = BigInt(this); 'use asm'; _this = _this|0n; n = n|0n; mask = mask|0n; return ((((_this & mask) + (n & mask))) ^ ((_this ^ n) & ~mask))|0n; } BigInt.prototype.PSUB = function(n, mask) { var _this = BigInt(this); 'use asm'; _this = _this|0n; n = n|0n; mask = mask|0n; return (((_this | ~mask) - (n & mask)) & mask) | ((_this - n) & ~mask)|0n; } Или нужно идти напролом без всякого сахара?:-? Может есть иные способы форсировать оптимизатор браузера? |
1. У меня на машине ничего не тормозит. :)
2. Это не геттеры - это просто расширения прототипа. (добавления методов в интерфейс родтельского класса, если вам так привычнее) Геттры - это неявно вычисляемые свойства. Вызов таких методов практически никак не отличается от вызова обычных функций, накладные расходы на поиск по цепочке наследования вверх пренебрежимо малы. 3. BigInt - может быть медленным, т.к. это не int64 или там int128, а длинная арифметика условно бесконечной длины. Для производительности стоит использовать ArrayBuffer и производные. 4. Скорее всего проблемы с производительностью и вовсе не в вызовах или математике, а во взаимодействии с браузерным api - тем же canvas например, но не поручусь - включите профалер в инструментах браузера и найдите что именно вызывает у вас тормоза, туда и смотрите. |
Вам уже не раз писали, что BigInt не имеет никакого отношения к 64 разрядным целым числам. Это вообще многобайтное (столько, сколько нужно) представление числа. Там может быть и 16 бит, и 64, и 128, и 1024...
Javascript вообще не умеет работать с 64 разрядными целыми числами. Точно представляются только целые числа меньше 2^53-1. Все, что больше представляются как Float64. Операция вила n = n|0n для BigInt вообще не имеет никакого смысла. Число никак не изменяется. Обычная операция для Number n = n|0 переводит в число в целое, отбрасывая дробную часть, и ограничивает целую часть 32 разрядами. Но BigInt и так целые, и разряды у них не ограничиваются. Цитата:
|
Clamped сложение/вычитание/сравнение - только предстоит брать!
Цитата:
У меня в Chromium под Raspbian 600MHz (лето: частоту занизил до плинтуса) линия на весь Canvas подвешивает браузер на 15-25 секунд и периодически выскакивает окошко с вопросом о принудительном завершении сценария!:lol: (Скрипт отлаживать в таких жарких и тормозных условиях чудовищно сложно.) Цитата:
Стоило в листинге даже закомментировать «.canvas rax» - всё залетало! Ещё притормаживает функция записи в журнал каждого снимка состояния регистров, которая к тому же безобразно написана: Два цикла с push'ами строк в массив. Но не так сильно, как ожидалось… Всё же Canvas.toDataURL чудовищно просаживает всю производительность!:yes: Буду искать другой метод: Не делать снимки всего Canvas, а просто сохранять координаты, куда ставить точки. Но это усложнит верхний уровень - реакция на «ползунок времени», где вместо простого вывода спрайта нужно будет пиксели стирать-ставить…:write: Цитата:
Хорошая фишка этот BigInt! Для AVX нужно уметь работать с 512 битным словом, да ещё и упакованным. Если сложение/вычитание ещё работает с упакованными данными по описанному мною трюку с масками выше, то сравнение и сложение/вычитание с насыщением (аналог Uint8ClampedArray) - очень сложная тема и как там с масками трюкачить - пока не понятно. Цитата:
|
Немного теории для введения в SIMD-симуляцию
Чтoбы понять все сложности и подводные камни технологии эмуляции SIMD-расчётов, рассмотрим несколько простейших операций.
Сутью SIMD-механизма является т.н. горизонтальное вычисление, когда одной операцией обрабатывается несколько данных, упакованных в одно слово. Например, сравним две функции сложения упакованных данных. Так как несущее слово имеет разрядность в 64 бита, то и дробление всех действий кратно длине упакованных данных в нём соответственно. Если данные - 8-битные слова, то в общем 64-битном умещается их восемь и функция выходит довольно длинной и скучной: // Сложение восьми 8-битных слов, упакованных в одно 64-битное слово BigInt.prototype.PADDB = function(n) { // Packed Add for Bytes var d1 = 0xFFn & this; // Распаковываем из 64-битного слова var d2 = 0xFFn & (this >> 8n); // восемь отдельных 8-битных var d3 = 0xFFn & (this >> 16n); var d4 = 0xFFn & (this >> 24n); var d5 = 0xFFn & (this >> 32n); var d6 = 0xFFn & (this >> 40n); var d7 = 0xFFn & (this >> 48n); var d8 = 0xFFn & (this >> 56n); var n1 = 0xFFn & n; // Распаковываем из 64-битного слова var n2 = 0xFFn & (n >> 8n); // восемь отдельных 8-битных var n3 = 0xFFn & (n >> 16n); var n4 = 0xFFn & (n >> 24n); var n5 = 0xFFn & (n >> 32n); var n6 = 0xFFn & (n >> 40n); var n7 = 0xFFn & (n >> 48n); var n8 = 0xFFn & (n >> 56n); var r1 = ((d1 + n1) & 0xFFn); // Складываем все восемь 8-битных слов var r2 = ((d2 + n2) & 0xFFn) << 8n; var r3 = ((d3 + n3) & 0xFFn) << 16n; var r4 = ((d4 + n4) & 0xFFn) << 24n; var r5 = ((d5 + n5) & 0xFFn) << 32n; var r6 = ((d6 + n6) & 0xFFn) << 40n; var r7 = ((d7 + n7) & 0xFFn) << 48n; var r8 = ((d8 + n8) & 0xFFn) << 56n; return r8 | r7 | r6 | r5 | r4 | r3 | r2 | r1; // Упаковываем всё обратно в 64-битное }Если же данные - 16-битные слова, то в 64-битном уместится их уже вдвое меньше, соответственно и функция сократится: // Сложение четырёх 16-битных слов, упакованных в одно 64-битное слово BigInt.prototype.PADDW = function(n) { // Packed Add for Words var d1 = 0xFFFFn & this; // Распаковываем из 64-битного слова var d2 = 0xFFFFn & (this >> 16n); // четыре отдельных 16-битных var d3 = 0xFFFFn & (this >> 32n); var d4 = 0xFFFFn & (this >> 48n); var n1 = 0xFFFFn & n; // Распаковываем из 64-битного слова var n2 = 0xFFFFn & (n >> 16n); // четыре отдельных 16-битных var n3 = 0xFFFFn & (n >> 32n); var n4 = 0xFFFFn & (n >> 48n); var r1 = ((d1 + n1) & 0xFFFFn); // Складываем все четыре 8-битных слов var r2 = ((d2 + n2) & 0xFFFFn) << 16n; var r3 = ((d3 + n3) & 0xFFFFn) << 32n; var r4 = ((d4 + n4) & 0xFFFFn) << 48n; return r4 | r3 | r2 | r1; // Упаковываем всё обратно в 64-битное }Но, как-то всё уныло и скучно получается. Да ещё и циклы применять крайне не желательно. Из-за чего исходный текст программы симуляции растёт как на дрожжах! Но, есть одна хитрость: Подавить признак переноса из одного разряда в другой в нескольких кратных позициях. Так, вместо неуклюжих функций выше получаем компактные такие две: Для 8-битных BigInt.prototype.PADDB = function(n) { return (((this & 0x7F7F7F7F7F7F7F7Fn) + (n & 0x7F7F7F7F7F7F7F7Fn))) ^ ((this ^ n) & 0x8080808080808080n); }И для 16-битных BigInt.prototype.PADDW = function(n) { return (((this & 0x7FFF7FFF7FFF7FFFn) + (n & 0x7FFF7FFF7FFF7FFFn))) ^ ((this ^ n) & 0x8000800080008000n); }К тому же, всё это хозяйство можно унифицировать до: BigInt.prototype.PADD = function(n, mask) { return (((this & mask) + (n & mask))) ^ ((this ^ n) & ~mask); }Только маску подставляй нужную и можно работать с упакованностью любой кратности! С вычитанием выходит немножечко посложнее, так как там не перенос подавить нужно, а предотвратить займ: BigInt.prototype.PSUB = function(n, mask) { return (((this | ~mask) - (n & mask)) & mask) | ((this - n) & ~mask); } Ну, здесь можно добавить сахарку и передавать маску в функцию неявным способом - (мой позорный вопрос №189 посвящён задаче маскировки маски), хотя там тоже свои нюансы. Если с нормальным сложением/вычитанием упакованных данных как бы всё очевидно, просто и изящно, то с операциями сравнения всё намного хуже! // Сравниваем восемь 8-битных слов и формируем восемь независимых масок в зависимости от результата сравнения BigInt.prototype.PCMPEQB = function(n) { var d1 = 0xFFn & this; // Распаковываем из 64-битного слова var d2 = 0xFFn & (this >> 8n); // восемь отдельных 8-битных var d3 = 0xFFn & (this >> 16n); var d4 = 0xFFn & (this >> 24n); var d5 = 0xFFn & (this >> 32n); var d6 = 0xFFn & (this >> 40n); var d7 = 0xFFn & (this >> 48n); var d8 = 0xFFn & (this >> 56n); var n1 = 0xFFn & n; // Распаковываем из 64-битного слова var n2 = 0xFFn & (n >> 8n); // восемь отдельных 8-битных var n3 = 0xFFn & (n >> 16n); var n4 = 0xFFn & (n >> 24n); var n5 = 0xFFn & (n >> 32n); var n6 = 0xFFn & (n >> 40n); var n7 = 0xFFn & (n >> 48n); var n8 = 0xFFn & (n >> 56n); var r1 = (d1 == n1 ? 0xFFn : 0x00n); // Сравниваем все восемь 8-битных слов var r2 = (d2 == n2 ? 0xFFn : 0x00n) << 8n; var r3 = (d3 == n3 ? 0xFFn : 0x00n) << 16n; var r4 = (d4 == n4 ? 0xFFn : 0x00n) << 24n; var r5 = (d5 == n5 ? 0xFFn : 0x00n) << 32n; var r6 = (d6 == n6 ? 0xFFn : 0x00n) << 40n; var r7 = (d7 == n7 ? 0xFFn : 0x00n) << 48n; var r8 = (d8 == n8 ? 0xFFn : 0x00n) << 56n; return r8 | r7 | r6 | r5 | r4 | r3 | r2 | r1; // Упаковываем всё обратно в 64-битное }Это просто тихий ужас! Как всё это оптимизировать - ума не приложу… А ведь это сравнение только 8-битных и только на эквивалентность! Из-за чего вся моя разработка затягивается на недели или откладывается. А учитывая, сколько всего команд уста ревшей технологии MMX желательно поддержать в симуляторе, да поглядывая ещё на SSE и 3DNow!, не говоря уж о AVX, то вопрос полной поддержки всего этого не стоит особняком, так как симулятор я разрабатывать начал для личных нужд и поддержку команд ввожу по мере надобности в них. И меня пока не привлекает тот факт, что BigInt поддерживает и 128, и 256, и 512 бит, так как даже представить весь тот ужас описания AVX-512 операций на нём невозможно! (Придётся халтурить и написать генератор дрожжевого кода в самом симуляторе при загрузке страницы, чтобы все громоздкие функции генериловались автоматически, не засоряя основной листинг симулятора…) Да, в интернете очень много профессиональных отладчиков, среди которых самый простой и удобный - бразильский: Шустро загружается, интерфейс без излишеств, легко запустить отладку. Но ни в каких отладчиках я не видел функции отката во времени: Если инструкция выполнилась, нельзя узнать про состояния регистров до её выполнения, иначе как перезапустить весь процесс с самого начала! Так и родилась идея данного симулятора, который журналирует исполнение каждой инструкции на каждой строчке и во множестве итераций цикла.:write: |
Цитата:
Мне кажется, проще перейти от BigInt к ArrayBufer и TypedArray. Да, там придется устраивать циклы. А вы думаете действия с BigInt работают напрямую? Там тоже циклы, только скрыты во внутренних библиотеках поддержки этих данных. Наверняка каждая операция - это цикл по атомарным единицам хранения этих чисел. |
Цитата:
Как быстро переполнится память на машине симуляторе? |
Ну очевидно тут что надо релизовать единый интерфейс n-битного числа, со всеми нужными методами операций как битовых, так и математических, после чего реализовать его для каждого нужного, неважно как: на bigint или arraybuffer, а то и в webassebly закодить.
А потом просто использовать прозрачно в вычислениях не заморачиваясь, что там под капотом, пока это не станет узким местом. Что-то типа такого, только и для всех больших битностей: https://github.com/dcodeIO/long.js#readme |
Цитата:
А циклы верхнего уровня ни в какое сравнение не идут по производительности и их лучше избегать в узких местах.:yes: Цитата:
Журналировать каждый доступ к памяти - дело крайне не благодарное. Но для работы со стеком я думаю на минимальном уровне как-то это обеспечить. В первую очередь, оболочка разрабатывалась для тестирования алгоритмов построения отрезков по Брезенхэму, где запросы к машине - минимальны, нет нужды в рекурсиях или стеке и не требуются тяжёлые вычисления. Цитата:
(Когда-то я уже делал подобное здесь: Там в «<textarea id=Pattern» псево-языком описывается шаблон генерирования сотен функций…) При эмуляции работы некоего процессора всегда чего-то не хватает из всего набора языка, так как процессор всегда работает немного иначе, чем можно выразить синтаксисом ЯВУ. Вот, например, операция Деления над массивом в JS отсутствует и её приходится реализовывать в цикле явно, хотя на уровне процессора такая инструкция есть и выполняется на порядок быстрее. Проблема в том, что авторы JS не предусматрели подобные матричные операции на базовом уровне. Что говорить, если процессор может одной командой переставить нужные члены массива, а в ЯВУ нужно явно самому их переставлять.:no: Так, в процессоре с поддержкой AVX-512 одно машинное слово может представляться как Uint8Array(64), Uint16Array(32), Uint32Array(16), Uint64Array(8) и т.д. То есть, здесь слово выступает как микромассив фиксированной длины и с гарантией, что над всеми членами этого массива практически любая операция будет выполняться параллельно. Сам синтаксис языка не поддерживает такой операции, хотя аппаратно она существует.;) Цитата:
Сейчас, как говорил выше, очень не хватает набора из Uint16ClampedArray, Int16ClampedArray, Uint32ClampedArray, Int32ClampedArray, Uint64ClampedArray, Int64ClampedArray и т.п. (сабж) Всё это есть в архитектурах с поддержкой SIMD, но языки как-то не поспевают в след за процессорами!:lol: Получается, будто объектная модель процессорного машинного кода в узких местах даже богаче, чем у JavaScript.:haha: К сожалению. |
Цитата:
Цитата:
|
Мб поможет чем-нить) https://github.com/tc39/ecmascript_simd
|
Цитата:
Просто подчёркиваю, что поддерживаемые аппаратно инструкции на целевой машине никак не доступны с верхнего уровня, хотя возникает парадокс, когда программист вынужден реализовывать матричные/векторные функции своими силами и мириться с чудовищной просадкой производительности, так как сам язык не поддерживает матрицы, но процессор - поддерживает.:agree: То есть, процессор вынужден выполнять эрзац-операцию из тысяч других, хотя эту эрзац-функцию сам процессор - знает и может выполнить мгновенно! В этом и суть парадокса!:D Шейдеры для 3D же в браузерах как-то поддерживаются, передавая их чуть ли не напрямую в видеопроцессор. А вот предусмотреть подобие шейдеров для самого процессора - нигде не смогли.:no: Цитата:
Когда можно было обойтись более простым - тем же Visual Basic Script. А вот GLSL- основан на ANSI C! Это чуть ли ни всю мощь Си в браузер перенесли пользователю на службу - избыточно как-то.:blink: Понимаете? Парадокс!:haha: Цитата:
Ресурс, конечно, устарел - написан до внедрения BigInt'ов, но он очень подробный и информативный. Хотя циклы там тоже имеются, что не хорошо для производительности. |
Eсть вот такой вопрос.
Вот часть кода: const X64_getters = { RAX :function() { var n = this[0]; n.WIDTH = 64; return n; }, EAX :function() { var n = this[0]; n = (n & 0xFFFFFFFFn) | (this.SIGNED && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n); n.WIDTH = 32; return n; }, AX :function() { var n = this[0]; n = (n & 0xFFFFn) | (this.SIGNED && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n); n.WIDTH = 16; return n; }, AL :function() { var n = this[0]; n = (n & 0xFFn) | (this.SIGNED && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 8; return n; }, AH :function() { var n = this[0] >> 8n; n = (n & 0xFFn) | (this.SIGNED && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 8; return n; }, SIGNED :function() { this.SIGNED = true; return this; } }; for(var fn in X64_getters) BigUint64Array.prototype.__defineGetter__(fn, X64_getters[fn]);Почему здесь попутный флаг SIGNED работает и передаётся внутрь функции, а вот ответное свойство WIDTH никак дальше не передаётся? Сейчас WIDTH мне необходимо как-то ввести для всё тех же операций с регистрами, так как необходимо знать ширину по имени регистра (сейчас это работает через имя регистра в строке с проверкой через switch-case, что не очень хорошо!) |
n.WIDTH = 64
Что такое n? number? object? Судя по коду это число, BigInt. И нет никаких сообщений об ошибке? Это ведь не объект, а примитивный тип. У него не может быть свойств |
Цитата:
WITH = { RAX:64, EAX:32, AX:16, .... } И проверяйте по имени без switch/case |
Думaю, без примера понять нельзя, так как я плохо понимаю (оказывается) объектную модель JavaScript.
<html> <head> <script> const X64_getters = { RAX :function() { var n = this[0]; n = (n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n); n.WIDTH = 16; return n; }, EAX :function() { var n = this[0]; n = (n & 0xFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n); n.WIDTH = 8; return n; }, AX :function() { var n = this[0]; n = (n & 0xFFFFn) | (this.EXPAND_SIGN && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n); n.WIDTH = 4; return n; }, AL :function() { var n = this[0]; n = (n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 2; return n; }, AH :function() { var n = this[0] >> 8n; n = (n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 2; return n; }, RBX :function() { var n = this[3]; n = (n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n); n.WIDTH = 16; return n; }, EBX :function() { var n = this[3]; n = (n & 0xFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n); n.WIDTH = 8; return n; }, BX :function() { var n = this[3]; n = (n & 0xFFFFn) | (this.EXPAND_SIGN && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n); n.WIDTH = 4; return n; }, BL :function() { var n = this[3]; n = (n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 2; return n; }, BH :function() { var n = this[3] >> 8n; n = (n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 2; return n; }, RCX :function() { var n = this[1]; n = (n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n); n.WIDTH = 16; return n; }, ECX :function() { var n = this[1]; n = (n & 0xFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n); n.WIDTH = 8; return n; }, CX :function() { var n = this[1]; n = (n & 0xFFFFn) | (this.EXPAND_SIGN && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n); n.WIDTH = 4; return n; }, CL :function() { var n = this[1]; n = (n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 2; return n; }, CH :function() { var n = this[1] >> 8n; n = (n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 2; return n; }, RDX :function() { var n = this[2]; n = (n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n); n.WIDTH = 16; return n; }, EDX :function() { var n = this[2]; n = (n & 0xFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n); n.WIDTH = 8; return n; }, DX :function() { var n = this[2]; n = (n & 0xFFFFn) | (this.EXPAND_SIGN && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n); n.WIDTH = 4; return n; }, DL :function() { var n = this[2]; n = (n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 2; return n; }, DH :function() { var n = this[2] >> 8n; n = (n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n); n.WIDTH = 2; return n; }, SIGNED :function() { this.EXPAND_SIGN = true; return this; } }; for(var fn in X64_getters) BigUint64Array.prototype.__defineGetter__(fn, X64_getters[fn]); var X64 = new BigUint64Array(16); X64[0] = 0xABCDEF0123456789n; X64[1] = 0x7654321089ABCDEFn; X64[2] = 0xF7E6D5C4B3A29180n; X64[3] = 0xF0E1D2C3B4A59687n; function main() { var Widths = { RAX :16, EAX :8, AX :4, AH :2, AL :2, RCX :16, ECX :8, CX :4, CH :2, CL :2, RDX :16, EDX :8, DX :4, DH :2, DL :2, RBX :16, EBX :8, BX :4, BH :2, BL :2 }; var hTable = document.getElementsByTagName("table")[0]; var hCells; var row, cell, regId, reg, sreg, width, content, flag, x64; for(row = 0; row < hTable.rows.length; ++ row) { hCells = hTable.rows[row].cells; for(cell = 0; cell < hCells.length; ++ cell) { hCells[cell].style.width = "100%"; hCells[cell].style.textAlign = "center"; if("reg" in hCells[cell].dataset) { regId = hCells[cell].dataset.reg.toUpperCase(); if(regId in X64) { reg = X64[regId]; // Если тут не создать копию массива, sreg = (new BigUint64Array(X64)).SIGNED[regId]; // опция SIGNED распространится не только на текущий момент чтения sreg - временно, // а на весь массив безвременно // sreg = X64.SIGNED[regId]; // Вот так - не работает: SIGNED установится насовсем! width = reg.WIDTH; flag = isFinite(width); if(!flag) width = regId in Widths ? Widths[regId] : 16; content = []; content.push("0".repeat(32) + reg.toString(16)); content.push("0".repeat(32) + sreg.toString(16)); hCells[cell].textContent = regId + ":" + (content[0].substr(-width * 2) + "/" + content[1].substr(-width * 2)).toUpperCase() + (flag ? "" : " !!!"); } else hCells[cell].textContent = "???"; } } } } </script> </head> <body onload='main()'> <table style=white-space:pre> <tr><td colspan=8 data-reg=RAX></td></tr> <tr><td colspan=4>---</td><td colspan=4 data-reg=EAX></td></tr> <tr><td colspan=6>---</td><td colspan=2 data-reg=AX></td></tr> <tr><td colspan=6>---</td><td data-reg=AH></td><td data-reg=AL></td></tr> <tr><td colspan=8 data-reg=RCX></td></tr> <tr><td colspan=4>---</td><td colspan=4 data-reg=ECX></td></tr> <tr><td colspan=6>---</td><td colspan=2 data-reg=CX></td></tr> <tr><td colspan=6>---</td><td data-reg=CH></td><td data-reg=CL></td></tr> <tr><td colspan=8 data-reg=RDX></td></tr> <tr><td colspan=4>---</td><td colspan=4 data-reg=EDX></td></tr> <tr><td colspan=6>---</td><td colspan=2 data-reg=DX></td></tr> <tr><td colspan=6>---</td><td data-reg=DH></td><td data-reg=DL></td></tr> <tr><td colspan=8 data-reg=RBX></td></tr> <tr><td colspan=4>---</td><td colspan=4 data-reg=EBX></td></tr> <tr><td colspan=6>---</td><td colspan=2 data-reg=BX></td></tr> <tr><td colspan=6>---</td><td data-reg=BH></td><td data-reg=BL></td></tr> </table> </body>Как видно, всюду - восклицательные знаки. Признак того, что ширину слова приходится читать из таблицы - getter её не передаёт. А ещё прроблема - необходимость копировать массив из-за безвременного действия опции SIGNED. Что я не понимаю и делаю не так?:) |
Цитата:
Цитата:
Все остальное - объекты. Свойства и методы могут быть только у объектов. Нельзя задать свойство примитивному типу Ну нельзя так let b = true; b.prop = 42; if (b.prop) {} Вы пишите n.WITH = 16. Но n - bigint. Примитивный тип. Цитата:
Т.е. например так RAX :function() { var n = this[0]; n = (n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n); /*n.WIDTH = 16; */ this.EXPAND_SIGN = false; //!!! return n; }, |
Цитата:
width = reg.WIDTH; flag = isFinite(width); if(!flag) width = regId in Widths ? Widths[regId] : 16;Как видно, всюду - восклицательные знаки. Признак того, что ширину слова приходится читать из таблицы - getter её не передаёт.:) Цитата:
Я тренировался специально над Number, чтобы затем завернуть всё в алгоритм выше. Но не работает!:p В чём подвох? Цитата:
|
Цитата:
Приведите рабочий пример, что бы я мог написать let n = 10 console.log (n.ME); или console.log (n.PERSON) У каких то примитивны типов есть объекты - обертки. При необходимости, примитивный тип превращается в объект со свойствами. Так 'abc'.length на самом деле выполняется, как (new String('abc')).length Т.е строка, сначала, временно превращается в объект со свойствами и методами. но потом этот объект исчезает, если конечно не присвоить его чему то let s = new String ('abc') s.BYTEWIDTH = 6; console.log (s.length, s.BYTEWIDTH) // 3 6 console.log (typeof s) // "object" !!!Но console.log (s + 'd') // 'abcd' !!!И даже Но вроде, как у BigInt нет такой оболочки |
Цитата:
Number.prototype.__defineGetter__ ("ME", function() { this.PERSON = "Me"; return this; }); Number.prototype.__defineGetter__ ("HERE", function() { this.PLACE = "Here"; return this; }); var Man = new Number(1536); var Logs = []; try { Logs.push(`Man: ${Man}`); } catch(e) {} try { Logs.push(`Man.ME: ${Man.ME}`); } catch(e) {} try { Logs.push(`Man.HERE: ${Man.HERE}`); } catch(e) {} try { Logs.push(`Man.ME.HERE: ${Man.ME.HERE}`); } catch(e) {} try { Logs.push(`Man.HERE.ME: ${Man.HERE.ME}`); } catch(e) {} try { Logs.push(`Man.ME.PERSON: ${Man.ME.PERSON}`); } catch(e) {} try { Logs.push(`Man.HERE.PLACE: ${Man.HERE.PLACE}`); } catch(e) {} try { Logs.push(`Man.ME.HERE.PLACE: ${Man.ME.HERE.PLACE}`); } catch(e) {} try { Logs.push(`Man.HERE.ME.PLACE: ${Man.HERE.ME.PLACE}`); } catch(e) {} try { Logs.push(`Man.ME.HERE.PERSON: ${Man.ME.HERE.PERSON}`); } catch(e) {} try { Logs.push(`Man.HERE.ME.PERSON: ${Man.HERE.ME.PERSON}`); } catch(e) {} console.log(Logs.join("\n")); alert(Logs.join("\n")); |
Ну я же спрашивал не про let n = new Number(42), а про let n = 42;
Хотя примитивный тип и обертка, ведут себя во многих случаях похоже, но все время приходится помнить, что это только обертка, и с ней нельзя делать все тоже, что и с примитивным значением. например не проскочит (из вашего примера) var Man = new Number(1536); Man += 1; Попробуйте выполнить все дальнейшее. Если Вас устроит подобная обертка для BigInt, то она, оказывается существует. Нельзя сделать, по аналогии с Number, new BigInt(x) Но можно Object(BigInt(x)) |
Теперь работает всё как и планировалось!
Цитата:
Цитата:
<html> <head> <script> const X64_getters = { RAX :function() { var n = this[0]; n = new Object(BigInt((n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n))); n.WIDTH = 16; this.EXPAND_SIGN = false; return n; }, EAX :function() { var n = this[0]; n = new Object(BigInt((n & 0xFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n))); n.WIDTH = 8; this.EXPAND_SIGN = false; return n; }, AX :function() { var n = this[0]; n = new Object(BigInt((n & 0xFFFFn) | (this.EXPAND_SIGN && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n))); n.WIDTH = 4; this.EXPAND_SIGN = false; return n; }, AL :function() { var n = this[0]; n = new Object(BigInt((n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n))); n.WIDTH = 2; this.EXPAND_SIGN = false; return n; }, AH :function() { var n = this[0] >> 8n; n = new Object(BigInt((n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n))); n.WIDTH = 2; this.EXPAND_SIGN = false; return n; }, RBX :function() { var n = this[3]; n = new Object(BigInt((n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n))); n.WIDTH = 16; this.EXPAND_SIGN = false; return n; }, EBX :function() { var n = this[3]; n = new Object(BigInt((n & 0xFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n))); n.WIDTH = 8; this.EXPAND_SIGN = false; return n; }, BX :function() { var n = this[3]; n = new Object(BigInt((n & 0xFFFFn) | (this.EXPAND_SIGN && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n))); n.WIDTH = 4; this.EXPAND_SIGN = false; return n; }, BL :function() { var n = this[3]; n = new Object(BigInt((n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n))); n.WIDTH = 2; this.EXPAND_SIGN = false; return n; }, BH :function() { var n = this[3] >> 8n; n = new Object(BigInt((n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n))); n.WIDTH = 2; this.EXPAND_SIGN = false; return n; }, RCX :function() { var n = this[1]; n = new Object(BigInt((n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n))); n.WIDTH = 16; this.EXPAND_SIGN = false; return n; }, ECX :function() { var n = this[1]; n = new Object(BigInt((n & 0xFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n))); n.WIDTH = 8; this.EXPAND_SIGN = false; return n; }, CX :function() { var n = this[1]; n = new Object(BigInt((n & 0xFFFFn) | (this.EXPAND_SIGN && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n))); n.WIDTH = 4; this.EXPAND_SIGN = false; return n; }, CL :function() { var n = this[1]; n = new Object(BigInt((n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n))); n.WIDTH = 2; this.EXPAND_SIGN = false; return n; }, CH :function() { var n = this[1] >> 8n; n = new Object(BigInt((n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n))); n.WIDTH = 2; this.EXPAND_SIGN = false; return n; }, RDX :function() { var n = this[2]; n = new Object(BigInt((n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n))); n.WIDTH = 16; this.EXPAND_SIGN = false; return n; }, EDX :function() { var n = this[2]; n = new Object(BigInt((n & 0xFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x80000000n) ? 0xFFFFFFFF00000000n : 0n))); n.WIDTH = 8; this.EXPAND_SIGN = false; return n; }, DX :function() { var n = this[2]; n = new Object(BigInt((n & 0xFFFFn) | (this.EXPAND_SIGN && (n & 0x8000n) ? 0xFFFFFFFFFFFF0000n : 0n))); n.WIDTH = 4; this.EXPAND_SIGN = false; return n; }, DL :function() { var n = this[2]; n = new Object(BigInt((n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n))); n.WIDTH = 2; this.EXPAND_SIGN = false; return n; }, DH :function() { var n = this[2] >> 8n; n = new Object(BigInt((n & 0xFFn) | (this.EXPAND_SIGN && (n & 0x80n) ? 0xFFFFFFFFFFFFFF00n : 0n))); n.WIDTH = 2; this.EXPAND_SIGN = false; return n; }, SIGNED :function() { this.EXPAND_SIGN = true; return this; } }; for(var fn in X64_getters) BigUint64Array.prototype.__defineGetter__(fn, X64_getters[fn]); var X64 = new BigUint64Array(16); X64[0] = 0xABCDEF0123456789n; X64[1] = 0x7654321089ABCDEFn; X64[2] = 0xF7E6D5C4B3A29180n; X64[3] = 0xF0E1D2C3B4A59687n; function main() { var Widths = { RAX :16, EAX :8, AX :4, AH :2, AL :2, RCX :16, ECX :8, CX :4, CH :2, CL :2, RDX :16, EDX :8, DX :4, DH :2, DL :2, RDX :16, EDX :8, DX :4, DH :2, DL :2 }; var hTable = document.getElementsByTagName("table")[0]; var hCells; var row, cell, regId, reg, sreg, width, content, flag, x64; for(row = 0; row < hTable.rows.length; ++ row) { hCells = hTable.rows[row].cells; for(cell = 0; cell < hCells.length; ++ cell) { hCells[cell].style.width = "100%"; hCells[cell].style.textAlign = "center"; if("reg" in hCells[cell].dataset) { regId = hCells[cell].dataset.reg.toUpperCase(); if(regId in X64) { reg = X64[regId]; sreg = X64.SIGNED[regId]; width = reg.WIDTH; flag = isFinite(width); if(!flag) width = regId in Widths ? Widths[regId] : 16; content = []; content.push("0".repeat(32) + reg.toString(16)); content.push("0".repeat(32) + sreg.toString(16)); hCells[cell].textContent = regId + ":" + (content[0].substr(-width * 2) + "/" + content[1].substr(-width * 2)).toUpperCase() + (flag ? "" : " !!!"); } else hCells[cell].textContent = "???"; } } } } </script> </head> <body onload='main()'> <table style=white-space:pre> <tr><td colspan=8 data-reg=RAX></td></tr> <tr><td colspan=4>---</td><td colspan=4 data-reg=EAX></td></tr> <tr><td colspan=6>---</td><td colspan=2 data-reg=AX></td></tr> <tr><td colspan=6>---</td><td data-reg=AH></td><td data-reg=AL></td></tr> <tr><td colspan=8 data-reg=RCX></td></tr> <tr><td colspan=4>---</td><td colspan=4 data-reg=ECX></td></tr> <tr><td colspan=6>---</td><td colspan=2 data-reg=CX></td></tr> <tr><td colspan=6>---</td><td data-reg=CH></td><td data-reg=CL></td></tr> <tr><td colspan=8 data-reg=RDX></td></tr> <tr><td colspan=4>---</td><td colspan=4 data-reg=EDX></td></tr> <tr><td colspan=6>---</td><td colspan=2 data-reg=DX></td></tr> <tr><td colspan=6>---</td><td data-reg=DH></td><td data-reg=DL></td></tr> <tr><td colspan=8 data-reg=RBX></td></tr> <tr><td colspan=4>---</td><td colspan=4 data-reg=EBX></td></tr> <tr><td colspan=6>---</td><td colspan=2 data-reg=BX></td></tr> <tr><td colspan=6>---</td><td data-reg=BH></td><td data-reg=BL></td></tr> </table> </body>Спасибо!:t hanks: P.S.: Правда, напрягает громоздкость и рутинность описания всех методов таким образом… Но, я сам выбрал такой путь…:haha: |
Если добавить
BigUint64Array.prototype._retval = function (n, w) { let v = Object(BigInt(n)); v.WIDTH = w; this.EXPAND_SIGN = false; return v; } То можно подсократить код RAX :function() { var n = this[0]; n = (n & 0xFFFFFFFFFFFFFFFFn) | (this.EXPAND_SIGN && (n & 0x8000000000000000n) ? 0xFFFFFFFFFFFFFFFF0000000000000000n : 0n); return this._retval(n, 16); }, |
Цитата:
С одной стороны - мой код и гроша не стоит, да и писал для себя. Но с другой - это же не очень хорошая практика плодить сущности!:no: |
Цитата:
Цитата:
Хорошей практикой было бы вообще не курочить прототипы встроенных объектов (это, кстати сказывается на производительности), а создать класс - наследник от BigUint64Array и все новые методы, геттеры и сеттеры определять в нем. |
Цитата:
Этот код и так из соседнего проекта (титул так и остался) с кучей рудиментов, а тот код - из другого проекта, так как лень парсер листинга с нуля писать. В итоге - я уже с трудом разбираюсь. Боюсь поломать.:p Цитата:
Соответственно, и парсер, и симулятор - всё в классы завернуть можно (и нужно)! Как только руки дойдут.:victory: |
Часовой пояс GMT +3, время: 22:07. |