├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── index.html ├── jquery.min.js ├── opa64.py ├── opv86.css ├── opv86.js └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.json 3 | *.txt 4 | *.pdf 5 | *.tar.gz 6 | *.xml 7 | *.html 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM ubuntu:latest 3 | 4 | # install prerequisites 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | RUN apt-get update -y && apt-get install -y python3 make 7 | 8 | # build data directory 9 | RUN mkdir -p /opa64/data 10 | 11 | COPY index.html opv86.css opv86.js jquery.min.js Makefile /opa64/ 12 | COPY data/db.json /opa64/data/ 13 | 14 | # run 15 | ENTRYPOINT ["make", "DIR=/data", "DB_DIR=/opa64/data", "SCRIPT_DIR=/opa64", "-f", "/opa64/Makefile", "start"] 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # document (pdfs and xmls) directory 3 | DIR = ./data 4 | 5 | # parsed json 6 | DB_DIR = $(DIR) 7 | DB_RAW = $(DB_DIR)/db.raw.json 8 | DB = $(DB_DIR)/db.json 9 | 10 | # js, python, and makefile 11 | SCRIPT_DIR = . 12 | SCRIPT = opa64.py 13 | 14 | PYTHON3 = python3 15 | 16 | all: $(DB) 17 | db: $(DB) 18 | 19 | $(DB_RAW): $(SCRIPT_DIR)/$(SCRIPT) 20 | $(PYTHON3) $(SCRIPT_DIR)/$(SCRIPT) fetch --doc=all --dir=$(DIR) 21 | $(PYTHON3) $(SCRIPT_DIR)/$(SCRIPT) parse --doc=all --dir=$(DIR) > $(DB_RAW) 22 | 23 | $(DB): $(DB_RAW) 24 | $(PYTHON3) $(SCRIPT_DIR)/$(SCRIPT) split --db=$(DB_RAW) > $(DB) 25 | 26 | start: 27 | $(PYTHON3) -m http.server 8080 --directory=$(SCRIPT_DIR) 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Armv8 A64 Assembly & Intrinsics Guide Server 3 | 4 | *— [developer.arm.com](https://developer.arm.com)に混じりてopを取りつつよろづのことに使ひけり —* 5 | 6 | ![screenshot comes here](./screenshot.png) 7 | 8 | ## Update 2021/10/25 9 | 10 | Arm seems to no longer publish a pdf version of the Intrinsics Guide on which this tool depends. Now that their [web version](https://developer.arm.com/architectures/instruction-sets/intrinsics/) has been updated with the latest instruction set, please refer to it. 11 | 12 | ## Running in Docker 13 | 14 | The following commands set up server at `http://localhost:8080/`. `` can be any directory outside the container where you want to save pdfs and database. Inside the container, the server script supposes the database is located at `/data`, so the path mapping would be like `-v $(pwd)/data:/data`, for example. On first launch, or more precisely if the database not found in `/data`, the script attempts to build it before starting the server. It would take a bit long, ~16min on Neoverse-N1 @2.3GHz and ~20min on Broadwell-U @2.2GHz. 15 | 16 | ``` 17 | $ docker build -t opa64server . 18 | $ docker run -it -v :/data -p 8080:8080 opa64server 19 | ``` 20 | 21 | *(You might see SSL warnings on fetching the documents.)* 22 | 23 | ## Running without Container 24 | 25 | ### Prerequisites 26 | 27 | * Python3 for fetching, parsing, and building database 28 | * **[Camelot](https://github.com/camelot-dev/camelot)** for parsing pdfs, available via `pip3 install camelot-py` 29 | * **OpenCV** and **Ghostscript** are internal dependencies of Camelot, available via `apt install python3-opencv ghostscript` for both Arm64 and x86\_64 on Ubuntu. If you are on x86\_64, you'll have some more alternative choices for OpenCV, such as `pip3 install opencv-python`. 30 | * **Requests** for fetching documents, available via `pip3 install requests`. 31 | * You might need to install **libgs** as the backend of the python ghostscript library. Available via `brew install ghostscript` on macOS or `apt install libgs-dev` on Ubuntu. 32 | 33 | ### Run 34 | 35 | `make db` builds the database in `data` directory and `make run` starts server at `http://localhost:8080/`. 36 | 37 | ```bash 38 | $ make db 39 | python3 opa64.py fetch --doc=all --dir=data 40 | python3 opa64.py parse --doc=all --dir=data > data/db.raw.json 41 | python3 opa64.py split --db data/db.raw.json > data/db.json 42 | $ make start 43 | python3 -m http.server 8080 44 | ``` 45 | 46 | ## List of Document Resources 47 | 48 | The script downloads the following documents. Currently the links are maintained manually so they might be behind the latest. Fixing them by issue or pull request is always welcome. 49 | 50 | * **"Arm A64 Instruction Set Architecture"** for descriptions of instructions: https://developer.arm.com/-/media/developer/products/architecture/armv8-a-architecture/2020-03/A64_ISA_xml_v86A-2020-03.tar.gz 51 | * **"Arm C Language Extensions Documentation"** for feature macros: https://static.docs.arm.com/101028/0011/ACLE_Q2_2020_101028_Final.pdf 52 | * **"Arm Neon Intrinsics Reference for ACLE"** for C/C++ intrinsics: https://static.docs.arm.com/ihi0073/e/IHI0073E_arm_neon_intrinsics_ref.pdf 53 | * **"Software Optimization Guides"** for latency & throughput tables 54 | * X1: [https://documentation-service.arm.com/static/5f15a74720b7cf4bc5247c06?token=] 55 | * A78: https://static.docs.arm.com/102160/0300/Arm_Cortex-A78_Core_Software_Optimization_Guide.pdf 56 | * A77: https://static.docs.arm.com/swog011050/c/Arm_Cortex-A77_Software_Optimization_Guide.pdf 57 | * A76: https://static.docs.arm.com/swog307215/a/Arm_Cortex-A76_Software_Optimization_Guide.pdf 58 | * N1: https://static.docs.arm.com/swog309707/a/Arm_Neoverse_N1_Software_Optimization_Guide.pdf 59 | * A75: https://static.docs.arm.com/101398/0200/arm_cortex_a75_software_optimization_guide_v2.pdf 60 | * A72: https://static.docs.arm.com/uan0016/a/cortex_a72_software_optimization_guide_external.pdf 61 | * A57: https://static.docs.arm.com/uan0015/b/Cortex_A57_Software_Optimization_Guide_external.pdf 62 | * A55: https://static.docs.arm.com/epm128372/30/arm_cortex_a55_software_optimization_guide_v3.pdf 63 | 64 | ## Notes 65 | 66 | This is an unofficial project providing an alternative tool to [the official NEON intrinsics guide](https://developer.arm.com/architectures/instruction-sets/simd-isas/neon/intrinsics). The main difference to the official one is it collects and gives non-NEON instructions, latency & throughput tables, and links to the original (detailed) html and pdf documents. Please make sure this is an unofficial one and not intended to be used as primary information, and the parsing result is not guaranteed to be correct. 67 | 68 | ## Copyright and License 69 | 70 | Large portion of the styles and codes in `opv86.css` and `index.html`, and some lines in `opv86.js` and `Makefile` were derived from [hikalium/opv86](https://github.com/hikalium/opv86). All the others in this repository except for `jquery.min.js` were written by Hajime Suzuki (@ocxtal). Everything in this repository is licensed under MIT (as in opv86). Note that **any document or content under developer.arm.com, including static.docs.arm.com, is not allowed for redistribution** according to [the license terms of the website](https://www.arm.com/en/company/policies/terms-and-conditions#our-content). 71 | 72 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Armv8 A64 Assembly & Intrinsics Guide 6 | 7 | 8 | 9 | 10 |

Armv8 A64 Assembly & Intrinsics Guide Instruction / Intrinsics finder for AArch64 processors

11 | 12 |
13 |
14 | Intrinsics only 15 | Non-SIMD/FP only 16 | SVE 17 | System 18 |
19 | 20 |
/
21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 db.raw.json 12 | $ python3 opa64.py split --db=db.raw.json > db.json 13 | 14 | The `fetch` command tries to download all the documents listed below as `urls`. If the argument 15 | is not `--doc=all`, such as `--doc=description` where `description` comes from the keys of `urls`, 16 | it fetches only the document. The `--doc` option allows multiple document keys. Nested elements 17 | in the `urls` (= `tables`) can be specified as `table.a78`. 18 | 19 | The `parse` command parses the pdf using Camelot library. If `--doc=all` option given, it parses 20 | all the documents listed in `urls` and concatenate them into single json. The root of the output 21 | json is dict, where two keys `metadata` and `insns` are always available. The `metadata` record 22 | keeps metadata for the document, such as path to the pdf. The `insns` record keeps the database 23 | as dict where canonized opcodes are used as keys. 24 | 25 | The `split` command takes the output of the `parse` command and compute appropriate opcode-to- 26 | -description and opcode-to-table (latency / throughput table parsed from Optimization Guides) 27 | mappings. This is needed because some instructions, such as add, ld, and so on ..., have multiple 28 | forms and variants. Since different description and latency / throughput record is given for each 29 | form or variant, we have to split and link them appropriately. The output of this command is 30 | also json, which contains `metadata` and `insns` keys as in the `parse` result. The output of 31 | `split` is feeded into `opv86.js` without modification. Note that keys in `insns` record is 32 | converted to shorthand forms, `instr-class` -> `ic`, `feature` -> `ft`, ..., to reduce the size 33 | of the output. 34 | """ 35 | import argparse 36 | import camelot 37 | import functools 38 | import itertools 39 | import json 40 | import os 41 | import re 42 | import requests 43 | import subprocess 44 | import sys 45 | import tarfile 46 | import time 47 | import xml.etree.ElementTree 48 | 49 | # hardcoded: sanitization table 50 | conv_singleline = str.maketrans({ '\t': '', '\xa0': '', '\xad': '', '‐': '', '\n': '', '\r': '' }) 51 | conv_multiline = str.maketrans({ '\t': '', '\xa0': '', '\xad': '', '‐': '' }) 52 | 53 | # hardcoded: doc url 54 | urls = { 55 | 'description': 'https://developer.arm.com/-/media/developer/products/architecture/armv8-a-architecture/2020-03/A64_ISA_xml_v86A-2020-03.tar.gz', 56 | 'intrinsics': 'https://static.docs.arm.com/ihi0073/e/IHI0073E_arm_neon_intrinsics_ref.pdf', 57 | 'table': { 58 | 'a78': 'https://static.docs.arm.com/102160/0300/Arm_Cortex-A78_Core_Software_Optimization_Guide.pdf', 59 | 'a77': 'https://static.docs.arm.com/swog011050/c/Arm_Cortex-A77_Software_Optimization_Guide.pdf', 60 | 'a76': 'https://static.docs.arm.com/swog307215/a/Arm_Cortex-A76_Software_Optimization_Guide.pdf', 61 | 'n1': 'https://static.docs.arm.com/swog309707/a/Arm_Neoverse_N1_Software_Optimization_Guide.pdf', 62 | 'a75': 'https://static.docs.arm.com/101398/0200/arm_cortex_a75_software_optimization_guide_v2.pdf', 63 | 'a72': 'https://static.docs.arm.com/uan0016/a/cortex_a72_software_optimization_guide_external.pdf', 64 | # 'a65': 'https://static.docs.arm.com/swog010045/a/Cortex_A65_Software_Optimization_Guide_1.0.pdf', 65 | # 'e1': 'https://static.docs.arm.com/swog466751/a/Neoverse_E1_Software_Optimization_Guide_1.0.pdf', 66 | 'a57': 'https://static.docs.arm.com/uan0015/b/Cortex_A57_Software_Optimization_Guide_external.pdf', 67 | 'a55': 'https://static.docs.arm.com/epm128372/30/arm_cortex_a55_software_optimization_guide_v3.pdf' 68 | }, 69 | 'macros': 'https://static.docs.arm.com/101028/0011/ACLE_Q2_2020_101028_Final.pdf' 70 | } 71 | macro_page_range = '34-39' # make sure the range covers entire list of feature macros 72 | 73 | 74 | # canonize opcode for use as matching tags 75 | def canonize_opcode(op_raw): 76 | fallback = { 77 | 'vmov': 'xtn', 78 | 'sra': 'ssra', 79 | 'revsh': 'rev', 80 | 'stadda': 'stadd', 81 | 'stclra': 'stclr', 82 | 'steora': 'steor', 83 | 'stseta': 'stset', 84 | 'stsmaxa': 'stsmax', 85 | 'stsmina': 'stsmin', 86 | 'stumaxa': 'stumax', 87 | 'stumina': 'stumin', 88 | 'staddal': 'staddl', 89 | 'staddalb': 'staddlb', 90 | 'staddalh': 'staddlh', 91 | 'stclral': 'stclrl', 92 | 'stclralb': 'stclrlb', 93 | 'stclralh': 'stclrlh', 94 | 'steoral': 'steorl', 95 | 'steoralb': 'steorlb', 96 | 'steoralh': 'steorlh', 97 | 'stsetal': 'stsetl', 98 | 'stsetalb': 'stsetlb', 99 | 'stsetalh': 'stsetlh', 100 | 'stsmaxal': 'stsmaxl', 101 | 'stsmaxalb': 'stsmaxlb', 102 | 'stsmaxalh': 'stsmaxlh', 103 | 'stsminal': 'stsminl', 104 | 'stsminalb': 'stsminlb', 105 | 'stsminalh': 'stsminlh', 106 | 'stumaxal': 'stumaxl', 107 | 'stumaxalb': 'stumaxlb', 108 | 'stumaxalh': 'stumaxlh', 109 | 'stuminal': 'stuminl', 110 | 'stuminalb': 'stuminlb', 111 | 'stuminalh': 'stuminlh' 112 | } 113 | if op_raw in fallback: return(fallback[op_raw]) 114 | return(re.split(r'[0-9\W\.]+', op_raw.strip('0123456789 '))[0]) 115 | 116 | # __ARM_FEATURE tag -> "ARMv8.n." conversion 117 | feature_abbrev = { 118 | 'crc32': 'crc', 119 | 'sha2': 'sha2', 120 | 'sha3': 'sha3', 121 | 'sm3': 'sm3', 122 | 'sm4': 'sm4', 123 | 'bf16': 'bf16', 124 | 'fp16': { 'scalar': 'fp16', 'fml': 'fhm' }, 125 | 'qrdmx': 'rdma', 126 | 'jcvt': 'jconv', 127 | 'dotprod': 'dotprod', 128 | 'complex': 'compnum', 129 | 'matmul': 'i8mm', 130 | 'frint': 'frint' 131 | } 132 | 133 | 134 | 135 | 136 | # logger 137 | starttime = time.monotonic() 138 | def message(msg): 139 | sys.stderr.write('[{:08.3f}] {}\n'.format(time.monotonic() - starttime, msg)) 140 | return 141 | 142 | def error(msg): 143 | message('error: {}'.format(msg)) 144 | return 145 | 146 | 147 | 148 | 149 | # utils 150 | def to_filepath(url, base): 151 | return(base + '/' + url.split('/')[-1]) 152 | 153 | def extract_filename(path): 154 | return(path.split('/')[-1]) 155 | 156 | def extract_base(path): 157 | return('/'.join(path.split('/')[:-1])) 158 | 159 | def canonize_doc_list(docs): 160 | doc_str = ','.join(docs) 161 | return([x.split('.') for x in doc_str.split(',')]) 162 | 163 | def build_doc_list(): 164 | def iterate_items(e): 165 | if type(e) is str: return([[e]]) 166 | return(sum([[[k] + x for x in iterate_items(v)] for k, v in e.items()], [])) 167 | return(['.'.join(x[:-1]) for x in iterate_items(urls)]) 168 | 169 | 170 | 171 | 172 | # fetch 173 | def fetch_file(url, base = '.', verify = True): 174 | # check the directory where pdf might have been saved already 175 | path = to_filepath(url, base) 176 | if os.path.exists(path): return(path) 177 | 178 | # if not, download it 179 | def fetch_file_intl(url, verify): 180 | with requests.get(url, verify = verify) as r: 181 | f = open(path, 'wb') 182 | f.write(r.content) 183 | f.close() 184 | 185 | try: 186 | fetch_file_intl(url, verify) 187 | except(requests.exceptions.SSLError): 188 | message('certificate verification failed. trying again without verification...') 189 | fetch_file_intl(url, False) 190 | time.sleep(1) 191 | return(path) 192 | 193 | 194 | 195 | 196 | # parse 197 | def parse_insn_table(path, page_range = 'all'): 198 | # I suppose all opcodes appear in the table is in the canonical form. so no need for canonizing them. 199 | def parse_opcodes(ops_str): 200 | def parse_paren(ops_str): 201 | m = re.match(r'(.+)[\({](.+)[\)}]', ops_str) # 'add{s}' -> ['add', 's'] 202 | (base, ext) = (m.group(1), m.group(2)) if m != None else (ops_str, '') # ['add', 's'] -> ['add', 'adds'] 203 | return([(canonize_opcode(x), x) for x in { base.strip(' '), base.strip(' ') + ext.strip(' ') }]) 204 | 205 | a = sum([parse_paren(x.strip(' ')) for x in re.split(r'[,/]+', ops_str)], []) 206 | return(a) 207 | 208 | def parse_iclass_itype(var_str): 209 | var_elems = [x.lower() for x in re.split(r'\W+', var_str)] 210 | if 'asimd' in var_elems: return('asimd', 'any') # too many variants in asimd 211 | if 'simd' in var_elems: return('asimd', 'any') 212 | if 'vector' in var_elems: return('asimd', 'any') 213 | if 'crypto' in var_elems: return('asimd', 'vector') # crypto is always asimd-vector 214 | if 'vfp' in var_elems: return('asimd', 'vector') # two types for FP subclass: vector, 215 | if 'fp' in var_elems: return('float', 'scalar') # and scalar 216 | return('general', 'scalar') # no instruction classified as general-vector 217 | 218 | def parse_variant(var_str): 219 | return([x.strip(' ') for x in var_str.split(',')]) 220 | 221 | # load table 222 | tables = camelot.read_pdf(path, pages = page_range) 223 | 224 | # parse table into opcode -> (form, latency, throughput, pipes, notes) mappings 225 | insns = dict() 226 | for t in tables: 227 | df = t.df.applymap(lambda x: x.translate(conv_singleline).lower()) 228 | if not df[0][0].startswith('instruction'): continue 229 | if not df[1][0].startswith('aarch64'): continue 230 | ops = sum([[(op_canon, op_raw, r) for op_canon, op_raw in parse_opcodes(r[1])] for i, r in df.iterrows() if i != 0], []) 231 | for op_canon, op_raw, r in ops: 232 | if op_canon not in insns: insns[op_canon] = [] 233 | (iclass, itype) = parse_iclass_itype(r[0]) 234 | variant = parse_variant(r[0]) 235 | insns[op_canon].append({ 236 | 'op_raw': op_raw, 237 | 'iclass': iclass, # for matching with intrinsics and description 238 | 'itype': itype, # for matching with intrinsics and description 239 | 'variant': variant, # to describe differences in the same - class 240 | 'latency': r[2], 241 | 'throughput': r[3], 242 | 'pipes': r[4], 243 | 'notes': r[5], 244 | 'page': t.page 245 | }) 246 | meta = { 'path': path } 247 | return({ 'metadata': meta, 'insns': insns }) 248 | 249 | 250 | 251 | 252 | def parse_intrinsics(path, page_range = 'all'): 253 | # reconstruct instruction sequence from joined string 254 | def recompose_sequence(asm_str): 255 | parts = asm_str.split(' ') 256 | delims = '+-*/%=(){}[]' 257 | (bin, join_more) = ([parts[0]], False) 258 | for p in parts[1:]: 259 | if p == '(scalar)': continue # workaround for FABD (scalar) Hd,Hn,Hm 260 | if join_more or p in delims: 261 | bin[-1] += ' ' + p 262 | join_more = True 263 | else: 264 | bin.append(p) 265 | join_more = False 266 | return(list(zip(bin[::2], (bin[1:] + [''])[::2]))) # list of (opcode operands) pairs 267 | 268 | # canonize opcode for use as matching tags 269 | def extract_opcode(intr_str, seq_canon): 270 | # tentative raw opcode and form parsed from the first one 271 | op_raw = seq_canon[0][0] # opcode of the first element of (opcode, operands) list 272 | # if expanded sequence is complex, try extract representative from function declaration 273 | if op_raw.startswith('result') or len(seq_canon) > 1: 274 | op_raw = re.split(r'[\W()]+', intr_str)[1].split('_')[0] 275 | if op_raw[0] == 'v': op_raw = op_raw[1:] # almost all function names begin with 'v' 276 | if op_raw[0] == 'q': op_raw = op_raw[1:] # workaround for vqtbx 277 | if op_raw[-1] == 'q': op_raw = op_raw[:-1] # 128bit-form 278 | # canonical opcode for matching with description and tables: 279 | # basically op_raw is used for matching descriptions, if not found, then fallback to op_canon 280 | op_canon = canonize_opcode(op_raw) 281 | return(op_canon, op_raw) 282 | 283 | # extract operand form from either intrinsics declaration or sequence of instructions 284 | def infer_inout_form(intr_str, seq_canon): 285 | # infer datatype from argument strings, for distinguishing half (sometimes armv8.2-fp16) instructions from single and double 286 | def aggregate_datatype(type_str): 287 | table = { 288 | 'uint8': '8', 'int8': '8', 'poly8': '8', 289 | 'uint16': '16', 'int16':'16', 'poly16':'16', 290 | 'uint32': '32', 'int32':'32', 'poly32':'32', 291 | 'uint64': '64', 'int64':'64', 'poly64':'64', 292 | 'float16': 'half', 'bfloat16': 'bf16', 293 | 'float32': 'single', 'float64': 'double' 294 | } 295 | type_base = [re.split(r'[_x]+', x)[0] for x in type_str] 296 | datatypes = list(set([table[x] for x in type_base if x in table])) 297 | return(datatypes) 298 | 299 | def type_to_signature(type_str): 300 | table = { 301 | 'uint8': ('b', 'b', 8), 'int8': ('b', 'b', 8), 'poly8': ('b', 'b', 8), 302 | 'uint16': ('h', 'h', 16), 'int16': ('h', 'h', 16), 'poly16': ('h', 'h', 16), 303 | 'uint32': ('w', 's', 32), 'int32': ('w', 's', 32), 'poly32': ('w', 's', 32), 304 | 'uint64': ('x', 'd', 64), 'int64': ('x', 'd', 64), 'poly64': ('x', 'd', 64), 305 | 'float16': ('h', 'h', 16), 'bfloat16': ('h', 'h', 16), 306 | 'float32': ('s', 's', 32), 'float64': ('d', 'd', 64) 307 | } 308 | 309 | tbs = re.split(r'[_x]+', type_str) 310 | if tbs[0] == 'const': return('i') # actually imm field (not a const variable), no datatype for imm 311 | if tbs[1] == 't' and tbs[0] in table: return(table[tbs[0]][0]) # fed to scalar pipe 312 | if tbs[1] == '1' and tbs[0] in table: return(table[tbs[0]][1]) # fed to vector (simdfp) pipe 313 | return('V' if int(tbs[1]) * table[tbs[0]][2] == 128 else 'v') # packed simd 314 | 315 | def operand_to_signature(operand): 316 | if operand == '': return('-') 317 | if operand[0] in 'bhwxrsdq': return(operand[0]) 318 | if operand[0] != 'v': return('-') 319 | # print(operand) 320 | return('V' if operand.split('.')[1] in ['16b', '8h', '4s', '2d', '4w', '2x'] else 'v') 321 | 322 | types = [x.strip(' ').split(' ')[0] for x in re.split(r'[\(\),]+', intr_str)] 323 | datatypes = aggregate_datatype(types) 324 | 325 | # if expanded sequence is complex, try extract form from function declaration 326 | # but this path is always fallback for emulated intrinsics, which generates multiple instructions, 327 | # because some instructions (loads and stores) have different arugument (operand) order between intrinsics and asmtemplate 328 | if len(seq_canon) > 1: 329 | sig = ''.join([type_to_signature(x) for x in types if x != '']) 330 | return(sig, datatypes) 331 | 332 | # simple (single-mnemonic) sequence; this path gives stable result 333 | if len(seq_canon) == 1: 334 | operands = [x.strip('{}') for x in seq_canon[0][1].split(',')] 335 | types = [operand_to_signature(x) for x in operands] 336 | imms = ['i' if ('[' in x and not x.startswith('[')) or x.startswith('imm') else '-' for x in operands] 337 | ptrs = ['x' if '[' in x and x.startswith('[') else '-' for x in operands] 338 | shift = ['i' if x.startswith('#') else '-' for x in operands] 339 | sig = ''.join(filter(lambda x: x != '-', sum([list(x) for x in zip(types, imms, ptrs, shift)], []))) 340 | return(sig, datatypes) 341 | 342 | # seems nop or no-operand instruction (system?) 343 | return('', []) 344 | 345 | # take two arguments: intrinsic function declaration, and sequences of instructions after expansion 346 | def parse_op_insns(intr_str, seq_canon): 347 | (op_canon, op_raw) = extract_opcode(intr_str, seq_canon) 348 | (form, datatypes) = infer_inout_form(intr_str, seq_canon) 349 | return(op_canon, op_raw, form, datatypes) 350 | 351 | # load table 352 | tables = camelot.read_pdf(path, pages = page_range) 353 | 354 | # parse table into opcode -> (intrinsics, arguments, mnemonic, result) mappings 355 | insns = dict() 356 | for t in tables: 357 | # print(t.df) 358 | df = t.df.applymap(lambda x: x.translate(conv_singleline).lower()) 359 | if not df[0][0].startswith('intrinsic'): continue 360 | for i, r in df.iterrows(): 361 | if i == 0: continue 362 | seq_canon = recompose_sequence(r[2]) 363 | # print(seq_canon) 364 | (op_canon, op_raw, form, datatypes) = parse_op_insns(r[0], seq_canon) 365 | if op_canon not in insns: insns[op_canon] = [] 366 | insns[op_canon].append({ 367 | 'op_raw': op_raw, 368 | 'form': form, 369 | 'datatypes': datatypes, 370 | 'intrinsics': r[0], 371 | 'sequence': [' '.join(list(x)).strip(' ') for x in seq_canon], 372 | 'page': t.page 373 | }) 374 | meta = { 'path': path } 375 | return({ 'metadata': meta, 'insns': insns }) 376 | 377 | 378 | 379 | 380 | # extract __ARM_FEATURE_xxx macros for C / C++, from Arm C / C++ Language Extension Spec. 381 | def parse_macros(path): 382 | def parse_macro_intl(macro_str): 383 | if not macro_str.startswith('__arm_feature_'): return(None, None) 384 | tags = macro_str[len('__arm_feature_'):].split('_') 385 | 386 | d = feature_abbrev 387 | for tag in tags: 388 | if tag not in d: return(None, None) 389 | d = d[tag] 390 | if type(d) is str: return(d, macro_str) 391 | return(None, None) 392 | 393 | # load table 394 | tables = camelot.read_pdf(path, macro_page_range) 395 | macros = dict() 396 | for t in tables: 397 | # print(t.df) 398 | df = t.df.applymap(lambda x: x.translate(conv_singleline).lower()) 399 | if not df[0][0].startswith('macro name'): continue 400 | for i, r in df.iterrows(): 401 | if i == 0: continue 402 | (feature, macro) = parse_macro_intl(r[0]) 403 | if feature == None: continue 404 | macros[feature] = { 405 | 'macro': macro, 406 | 'page': t.page 407 | } 408 | meta = { 'path': path } 409 | return({ 'metadata': meta, 'insns': macros }) 410 | 411 | 412 | 413 | 414 | # extract instruction description from ISA xml files (far easier from extracting them from pdf) 415 | def prepare_expanded_tarfile(path): 416 | tar = tarfile.open(path) 417 | files = [x.name for x in filter(lambda x: x.name.endswith('.xml'), tar.getmembers())] 418 | dirs = sorted(list(set([x.split('/')[1] for x in files]))) # suppose starts with './' 419 | 420 | if len(dirs) != 2 or dirs[0] + '_OPT' != dirs[1]: 421 | # unknown directory structure 422 | return(None, None, None) 423 | 424 | tar_intl_xml_dir = '/'.join(['.', dirs[1]]) 425 | files = list(filter(lambda x: x.startswith(tar_intl_xml_dir), files)) 426 | 427 | # untar directory if needed 428 | if not os.path.exists(tar_intl_xml_dir): 429 | tar.extractall(extract_base(path)) 430 | 431 | html_dir = '/'.join([extract_base(path), dirs[1], '']) # use optimized-pseudocode variant 432 | return(html_dir, tar, files) 433 | 434 | def parse_insn_xml(path): 435 | # extract and concatenate all text under a node 436 | def dump_text(nodes, remove_newlines = True): 437 | def dump_text_intl(n, acc): 438 | if n.text != None: acc += n.text 439 | for c in n: acc = dump_text_intl(c, acc) 440 | if n.tail != None: acc += n.tail 441 | return(acc) 442 | 443 | (conv, st) = (conv_singleline, '\t ') if remove_newlines else (conv_multiline, '\r\n\t ') 444 | s = ' '.join([dump_text_intl(n, '').translate(conv).strip(st) for n in nodes]) 445 | return(re.sub(r'\s+', ' ', s) if remove_newlines else s) 446 | 447 | def canonize_asm(asm): 448 | (op_raw, operands) = tuple([x.strip(' ') for x in (asm + ' ').split(' ', 1)]) 449 | if operands.startswith('{2}'): 450 | op_raw += '{2}' 451 | operands = operands[3:] 452 | if operands.startswith(' <'): # bfmlal workaround 453 | operands = operands[5:] 454 | operands = ''.join(list(filter(lambda x: x not in '<> ', operands))) 455 | return(' '.join([op_raw, operands]).lower()) 456 | 457 | # instruction class and corresponding forms 458 | def parse_attributes(root): 459 | def format_form(asm): 460 | def parse_form(operand, rxs): 461 | if len(rxs) == 0: return(operand) 462 | operand_parts = filter(lambda x: x != '', [x.strip(' ') for x in re.split(rxs[0], operand)]) 463 | return(list(filter(lambda x: x != [], [parse_form(x, rxs[1:]) for x in operand_parts]))) 464 | 465 | def map_form(operands, depth): 466 | if depth == 0: 467 | if operands.startswith('#'): return(['i']) 468 | if operands.startswith('('): return([(operands.strip('<>()') + ' ')[0], '']) 469 | return([operands.strip('<>')[0]]) 470 | e = [map_form(x, depth - 1) for x in operands] 471 | if depth != 1: e = [x for i, x in enumerate(e)] 472 | parts = [''.join(x) for x in itertools.product(*e)] 473 | return(list(set(parts))) 474 | 475 | # canonical form is ignored here; is parsed from ./desc/description 476 | (op_raw, operands) = tuple((asm + ' ').split(' ', 1)) 477 | delims = [r'[\[\]]+', r'[\{\}]+', r'[, ]+'] 478 | return(map_form(parse_form(''.join(filter(lambda x: x != ' ', operands)), delims), len(delims))) 479 | 480 | attrs = [] 481 | iclasses = root.findall('./classes/iclass') 482 | for iclass in iclasses: 483 | attr = dict() 484 | for x in iclass.findall('./docvars/docvar'): 485 | attr[x.attrib['key'].lower()] = x.attrib['value'].lower() 486 | for x in iclass.findall('./arch_variants/arch_variant'): 487 | # general and advsimd 488 | if 'name' in x.attrib: attr['gen'] = x.attrib['name'].lower() 489 | if 'feature' in x.attrib: attr['feature'] = x.attrib['feature'].lower() 490 | 491 | # an instruction might have multiple forms 492 | asms = [canonize_asm(dump_text(x).lower()) for x in iclass.findall('./encoding/asmtemplate')] 493 | attr['forms'] = list(set(sum([format_form(a) for a in asms], []))) # dedup 494 | attr['asm'] = asms 495 | attr['equiv'] = canonize_asm(dump_text(iclass.findall('./encoding/equivalent_to/asmtemplate'))).strip(' ') 496 | attrs.append(attr) 497 | return(attrs) 498 | 499 | def extract_opcodes(root): 500 | # priority: alias_mnemonic > mnemonic > id 501 | opcodes = filter(str.isupper, re.split(r'[\W,]+', dump_text(root.findall('./heading')))) 502 | opcodes = filter(lambda x: x != 'simd' and x != 'fp', [x.lower() for x in opcodes]) 503 | return(list(set([canonize_opcode(x) for x in opcodes]))) 504 | 505 | # extract filenames and directory for creating link 506 | (dir, tar, files) = prepare_expanded_tarfile(path) 507 | meta = { 'path': path, 'htmldir': dir + 'xhtml/' } 508 | insns = dict() 509 | for file in files: 510 | content = b''.join([x for x in tar.extractfile(file).readlines()]) 511 | root = xml.etree.ElementTree.fromstring(content.decode('UTF-8')) 512 | if root.tag != 'instructionsection': continue 513 | if 'type' in root.attrib and root.attrib['type'] == 'pseudocode': continue 514 | 515 | docvars = root.findall('./docvars/docvar') 516 | 517 | # skip_list = ['sve', 'system'] # skip sve and system instructions if needed 518 | skip_list = [] 519 | if functools.reduce(lambda x, y: x or y.attrib['value'].lower() in skip_list, docvars, False): continue 520 | 521 | for op in extract_opcodes(root): 522 | if op not in insns: insns[op] = [] 523 | insns[op].append({ 524 | 'file': extract_filename(file).replace('.xml', '.html'), 525 | 'attrs': parse_attributes(root), 526 | 'brief': dump_text(root.findall('./desc/brief')), 527 | 'desc': ' '.join([dump_text(root.findall(k)) for k in ['./desc/description', './desc/authored']]), 528 | 'operation': dump_text(root.findall('./ps_section'), False) 529 | }) 530 | return({ 'metadata': meta, 'insns': insns }) 531 | 532 | 533 | 534 | 535 | # fetch -> parse -> concatenate (split not here) 536 | def fetch_all(doc_list, base = '.'): 537 | docs = canonize_doc_list(doc_list) 538 | for doc in docs: 539 | if not doc[0] in urls: 540 | error('unknown document specifier: --doc={}'.format(doc[0])) 541 | continue 542 | 543 | if type(urls[doc[0]]) is str: 544 | message('fetching {}... ({})'.format(doc[0], urls[doc[0]])) 545 | fetch_file(urls[doc[0]], base) 546 | continue 547 | 548 | archs = urls[doc[0]].keys() if len(doc) == 1 else [doc[1]] 549 | for arch in archs: 550 | message('fetching {}.{}... ({})'.format(doc[0], arch, urls[doc[0]][arch])) 551 | fetch_file(urls[doc[0]][arch], base) 552 | return(None) 553 | 554 | def parse_one(doc, base = '.'): 555 | if not doc[0] in urls: 556 | error('unknown document specifier: --doc={}'.format(doc[0])) 557 | return(None) 558 | 559 | def to_filepath_with_check(url, base): 560 | path = to_filepath(url, base) 561 | if not os.path.exists(path): 562 | error('file not found: {} (might be \'--dir\' missing or wrong)'.format(path)) 563 | return(None) 564 | return(path) 565 | 566 | if type(urls[doc[0]]) is str: 567 | fnmap = { 568 | 'description': parse_insn_xml, 569 | 'intrinsics': parse_intrinsics, 570 | 'macros': parse_macros 571 | } 572 | if doc[0] not in fnmap: return(None) 573 | fn = fnmap[doc[0]] 574 | path = to_filepath_with_check(urls[doc[0]], base) 575 | return(fn(path) if path != None else None) 576 | 577 | if len(doc) == 1 or doc[1] not in urls[doc[0]]: 578 | error('second specifier needed for --doc=table, one of [\'a78\', \'a77\', \'a76\', \'n1\', \'a75\', \'a72\', \'a57\', \'a55\']') 579 | return(None) 580 | path = to_filepath_with_check(urls[doc[0]][doc[1]], base) 581 | return(parse_insn_table(path) if path != None else None) 582 | 583 | def parse_all(doc_list, base = '.'): 584 | docs = canonize_doc_list(doc_list) 585 | if len(docs) == 1: return(parse_one(docs[0], base)) 586 | 587 | def update_db(db, doc, db_ret): 588 | def update_dict(dic, ks, v): 589 | if len(ks) == 1: 590 | dic[ks[0]] = v 591 | return(dic) 592 | if ks[0] not in dic: dic[ks[0]] = dict() 593 | dic[ks[0]] = update_dict(dic[ks[0]], ks[1:], v) 594 | return(dic) 595 | 596 | for k, v in db_ret.items(): db = update_dict(db, [k] + doc, v) 597 | return(db) 598 | 599 | def update_feature_macro(insns, unused, macros): 600 | for insn in insns: 601 | if 'description' not in insns[insn]: continue 602 | descs = insns[insn]['description'] 603 | for i in range(len(descs)): 604 | attrs = descs[i]['attrs'] 605 | for j in range(len(attrs)): 606 | if 'feature' not in attrs[j]: continue 607 | tag = attrs[j]['feature'].split('-')[-1] 608 | if tag not in macros: continue 609 | attrs[j]['macro'] = macros[tag] 610 | descs[i]['attrs'] = attrs 611 | insns[insn]['description'] = descs 612 | return(insns) 613 | 614 | meta = dict() 615 | insns = dict() 616 | for doc in docs: 617 | # forks process, as workaround for a bug in ghostscript. calling some API in libgs.so, 618 | # which is done inside camelot, makes `/etc/papersize` left open, and calling the API several hundred times 619 | # uses up the fd resource of the operating system. to avoid this without fixing the bug is dividing parsing 620 | # into multiple units and doing each in disjoint processes. 621 | doc_str = '.'.join(doc) 622 | cmd = '{} {} parse --doc={} --dir={}'.format(sys.executable, os.path.realpath(sys.argv[0]), doc_str, base) 623 | message('parsing {}... (command: {})'.format(doc_str, cmd)) 624 | ret = subprocess.run(cmd, shell = True, capture_output = True) 625 | db = json.loads(ret.stdout) 626 | 627 | # update metadata db 628 | meta = update_db(meta, doc, db['metadata']) 629 | 630 | # update instruction db 631 | fn = update_db if doc[0] != 'macros' else update_feature_macro 632 | insns = fn(insns, doc, db['insns']) 633 | return({ 'metadata': meta, 'insns': insns }) 634 | 635 | 636 | 637 | 638 | # split and reorder database 639 | def merge_attrs(op_canon, attrs): 640 | def is_op_in_asm(op_canon, attrs): 641 | if 'asm' not in attrs: return(False) 642 | ops = [canonize_opcode(x.split(' ')[0]) for x in attrs['asm']] 643 | return(op_canon in ops) 644 | 645 | def merge_attrs_core(attr, a): 646 | for k in a: 647 | if k not in attr: 648 | attr[k] = a[k] 649 | continue 650 | if k in attr and attr[k] == a[k]: continue 651 | if type(attr[k]) is str: 652 | attr[k] += ', ' + a[k] # string 653 | else: 654 | attr[k] += a[k] # list 655 | return(attr) 656 | 657 | attrs_filtered = list(filter(lambda x: is_op_in_asm(op_canon, x), attrs)) 658 | if len(attrs_filtered) > 0: attrs = attrs_filtered 659 | 660 | attr = attrs[0] 661 | for a in attrs[1:]: attr = merge_attrs_core(attr, a) 662 | # print(attr) 663 | return(attr) 664 | 665 | def filter_descs_and_tables(op_canon, intr, descs, tables): 666 | def filter_descs_by_form(intr, descs): 667 | def revmap_core(form): 668 | return(form.translate(str.maketrans({ 'v': 's', 'r': 's' }))) 669 | def revmap(attr, only_simd = True): 670 | if only_simd and 'advsimd-type' not in attr: return(attr['forms']) 671 | if only_simd and attr['advsimd-type'] == 'simd': return(attr['forms']) 672 | forms = [x.translate(str.maketrans({ 'v': 's', 'r': 's' })) for x in attr['forms']] 673 | return(forms) 674 | def squash(form): 675 | return(form.translate(str.maketrans({ 'b': 's', 'h': 's', 'w': 's', 'x': 's', 'd': 's', 'v': 's', 'r': 's' }))) 676 | 677 | conv = str.maketrans({ 'b': 'r', 'h': 'r', 'w': 'r', 'x': 'r', 's': 'v', 'd': 'v' }) 678 | fn1s = [ 679 | lambda form, attr: form in attr['forms'], 680 | lambda form, attr: form.lower() in attr['forms'], 681 | lambda form, attr: form in [x.translate(conv) for x in attr['forms']], 682 | lambda form, attr: form.lower() in [x.translate(conv) for x in attr['forms']], 683 | lambda form, attr: form in revmap(attr), 684 | lambda form, attr: form.lower() in revmap(attr), 685 | lambda form, attr: form in revmap(attr, False), 686 | lambda form, attr: form.lower() in revmap(attr, False), 687 | lambda form, attr: squash(form) in revmap(attr), 688 | lambda form, attr: squash(form.lower()) in revmap(attr), 689 | lambda form, attr: squash(form) in revmap(attr, False), 690 | lambda form, attr: squash(form.lower()) in revmap(attr, False) 691 | ] 692 | 693 | fn2s = [ 694 | lambda fn1, intr, form, x: fn1(form, x['attr']), 695 | lambda fn1, intr, form, x: ('mnemonic' not in x['attr']) or (intr['op_raw'] == x['attr']['mnemonic']), 696 | lambda fn1, intr, form, x: ('asm' not in x['attr']) or (intr['op_raw'] in [x.split(' ')[0] for x in x['attr']['asm']]), 697 | lambda fn1, intr, form, x: ('datatype' not in x['attr']) or (len(set(intr['datatypes']) & set(x['attr']['datatype'].split('-'))) > 0) 698 | ] 699 | 700 | def combine_form(x): 701 | return([x, x[1:], x[0] + x, x[0] + x[0] + x, x[0] + x[0] + x[0] + x, x + 'wea']) 702 | 703 | # for debugging 704 | def print_descs(i, j, form, fds): 705 | ds = [( 706 | form, 707 | squash(form.lower()), 708 | x['attr']['forms'] if 'forms' in x['attr'] else '---', 709 | revmap(x['attr']), 710 | revmap(x['attr'], False), 711 | intr['op_raw'], 712 | x['attr']['mnemonic'] if 'mnemonic' in x['attr'] else '---', 713 | intr['datatypes'], 714 | x['attr']['datatype'] if 'datatype' in x['attr'] else '---', 715 | x['attr']['advsimd-type'] if 'advsimd-type' in x['attr'] else '---', 716 | [x.split(' ')[0] for x in x['attr']['asm']] if 'asm' in x['attr'] else '---') for x in fds 717 | ] 718 | for d in ds: print(i, j, d) 719 | return 720 | 721 | if 'form' not in intr or len(intr['form']) == 0: return(None) 722 | for form in combine_form(intr['form']): 723 | for i, fn1 in enumerate(fn1s): 724 | filtered_descs = sum([[{ 'desc': d, 'attr': a } for a in d['attrs']] for d in descs], []) 725 | # print_descs(i, 0, form, filtered_descs) 726 | for j, fn2 in enumerate(fn2s): 727 | filtered_descs = list(filter(lambda x: fn2(fn1, intr, form, x), filtered_descs)) 728 | # print_descs(i, j + 1, form, filtered_descs) 729 | if len(filtered_descs) == 0: break 730 | if len(filtered_descs) == 1: return(filtered_descs[0]) 731 | return(None) 732 | 733 | # for debugging 734 | print('failed filtering') 735 | for d in descs: 736 | for a in d['attrs']: 737 | print(a['asm'], intr['form'].lower(), a['forms'], a['advsimd-type'] if 'advsimd-type' in a else '---', squash(intr['form']), [x.translate(conv) for x in a['forms']], revmap(a), revmap(a, True)) 738 | return(None) 739 | 740 | def filter_tables_by_form(attr, tables): 741 | if 'instr-class' not in attr: return(tables) 742 | canon_class = { 'advsimd': 'asimd', 'fpsimd': 'asimd', 'float': 'float', 'general': 'general', 'system': 'system' } 743 | # print(attr, table) 744 | iclass = canon_class[attr['instr-class']] 745 | return(list(filter(lambda x: x['iclass'] == iclass, tables))) 746 | 747 | # check form available in intrinsics, impossible to filter descriptions if none 748 | if 'form' not in intr: return([({ 'desc': d, 'attr': merge_attrs(op_canon, d['attrs']) }, tables) for d in descs]) 749 | 750 | # first try filtering descriptions by form 751 | filtered_descs = filter_descs_by_form(intr, descs) 752 | if filtered_descs == None: return(None) 753 | 754 | # gather latency table that are related to the class; table is dict, table[processor] is list 755 | filtered_table = dict([(proc, filter_tables_by_form(filtered_descs['attr'], tables[proc])) for proc in tables]) 756 | return([(filtered_descs, filtered_table)]) 757 | 758 | def split_insns(filename): 759 | def find_or(d, k, o = ''): 760 | return(d[k] if k in d else o) 761 | 762 | def copy_as(dst, dkey, src, skey): 763 | if skey not in src: return(dst) 764 | dst[dkey] = src[skey] 765 | return(dst) 766 | 767 | def compose_brief(op_canon, intr, desc): 768 | brief = { 769 | 'ic': find_or(desc['attr'], 'instr-class'), 770 | 'ft': find_or(desc['attr'], 'feature'), 771 | 'op': find_or(intr, 'op_raw', op_canon), 772 | 'it': find_or(intr, 'intrinsics') 773 | } 774 | brief = copy_as(brief, 'ip', intr, 'page') 775 | brief = copy_as(brief, 'mc', desc['attr'], 'macro') 776 | brief = copy_as(brief, 'as', desc['attr'], 'asm') 777 | brief = copy_as(brief, 'eq', desc['attr'], 'equiv') 778 | brief = copy_as(brief, 'cs', desc['attr'], 'cond-setting') 779 | brief = copy_as(brief, 'rf', desc['desc'], 'file') 780 | return(brief) 781 | 782 | def compose_description(op_canon, intr, desc): 783 | description = { 784 | 'bf': find_or(desc['desc'], 'brief'), 785 | 'dt': find_or(desc['desc'], 'desc'), 786 | 'or': find_or(desc['desc'], 'operation') 787 | } 788 | return(description) 789 | 790 | def compose_tables(ts): 791 | def compose_table(t, com): 792 | table = { 793 | # 'op': find_or(t, 'op_raw'), 794 | 'vr': list(filter(lambda x: x not in com, find_or(t, 'variant', []))), 795 | 'lt': find_or(t, 'latency'), 796 | 'tp': find_or(t, 'throughput'), 797 | 'ip': find_or(t, 'pipes'), 798 | 'pp': find_or(t, 'page'), 799 | } 800 | return(table) 801 | 802 | if len(ts) == 0: return(ts) 803 | all = set(sum([x['variant'] for x in sum([v for v in ts.values()], [])], [])) 804 | common = functools.reduce(lambda x, y: x & set(y['variant']), sum([v for v in ts.values()], []), all) 805 | 806 | tables = dict() 807 | for k, ts in ts.items(): tables[k] = [compose_table(t, common) for t in ts] 808 | return(tables) 809 | 810 | def compose_blank(op_canon, intr): 811 | # print('blank', op_canon) 812 | # print(intr) 813 | brief = { 814 | 'ic': 'advsimd' if op_canon in ['zip', 'uzp', 'trn', 'cmla', 'combine', 'dup'] else 'unknown', 815 | 'ft': '', 816 | 'op': find_or(intr, 'op_raw', op_canon), 817 | 'it': find_or(intr, 'intrinsics'), 818 | } 819 | brief = copy_as(brief, 'ip', intr, 'page') 820 | brief = copy_as(brief, 'as', intr, 'sequence') 821 | return({ 822 | 'bf': brief, 823 | 'ds': { 'bf': '', 'dt': '', 'or': '' }, 824 | 'tb': [] 825 | }) 826 | 827 | def split_insns_intl(op_canon, v): 828 | if op_canon == '': return([]) 829 | 830 | tables = v['table'] if 'table' in v else dict() 831 | intrs = v['intrinsics'] if 'intrinsics' in v else [dict()] 832 | 833 | if 'description' not in v: return([compose_blank(op_canon, i) for i in intrs]) 834 | 835 | # for each instruction class 836 | descs = v['description'] 837 | for i in range(len(descs)): descs[i]['index'] = i 838 | 839 | insns = [] 840 | for intr in intrs: 841 | # print(op_canon, intr) 842 | xs = filter_descs_and_tables(op_canon, intr, descs, tables) 843 | if xs == None: 844 | insns.append(compose_blank(op_canon, intr)) 845 | continue 846 | # print('desc: ', d) 847 | # print('table: ', t) 848 | insns.extend([{ 849 | 'bf': compose_brief(op_canon, intr, d), 850 | 'ds': compose_description(op_canon, intr, d), 851 | 'tb': compose_tables(ts), 852 | 'index': d['desc']['index'] 853 | } for d, ts in xs]) 854 | 855 | # print(insns) 856 | covered = set([x['index'] for x in insns if 'index' in x]) 857 | for i in range(len(descs)): 858 | if i in covered: continue 859 | d = { 860 | 'attr': merge_attrs(op_canon, descs[i]['attrs']), 861 | 'desc': descs[i] 862 | } 863 | insns.append({ 864 | 'bf': compose_brief(op_canon, {}, d), 865 | 'ds': compose_description(op_canon, {}, d), 866 | 'tb': [], 867 | 'index': i 868 | }) 869 | for insn in insns: insn.pop('index', None) 870 | return(insns) 871 | 872 | # read json file 873 | meta = dict() 874 | insns = [] 875 | with open(filename) as f: 876 | db = json.load(f) 877 | meta = db['metadata'] 878 | for op, v in db['insns'].items(): insns.extend(split_insns_intl(op, v)) 879 | insns.sort(key = lambda x: x['bf']['op'] if 'bf' in x else '') 880 | return({ 'metadata': meta, 'insns': insns }) 881 | 882 | 883 | 884 | 885 | if __name__ == '__main__': 886 | ap = argparse.ArgumentParser( 887 | description = 'fetch and parse AArch64 ISA and intrinsics documentation' 888 | ) 889 | 890 | # subcommands 891 | sub = ap.add_subparsers() 892 | fa = sub.add_parser('fetch') 893 | fa.set_defaults(func = fetch_all) 894 | fa.add_argument('--dir', 895 | action = 'store', 896 | help = 'working directory where downloaded documents are saved', 897 | default = '.' 898 | ) 899 | fa.add_argument('--doc', 900 | action = 'append', 901 | help = 'list of documents to fetch, one or more of [\'intrinsics\', \'table\', \'description\'], or \'all\' for everything', 902 | default = [] 903 | ) 904 | 905 | pa = sub.add_parser('parse') 906 | pa.set_defaults(func = parse_all) 907 | pa.add_argument('--dir', 908 | action = 'store', 909 | help = 'working directory where downloaded documents are saved', 910 | default = '.' 911 | ) 912 | pa.add_argument('--doc', 913 | action = 'append', 914 | help = 'list of documents to fetch, one or more of [\'intrinsics\', \'table\', \'description\'], or \'all\' for everything', 915 | default = [] 916 | ) 917 | 918 | pa = sub.add_parser('split') 919 | pa.set_defaults(func = split_insns) 920 | pa.add_argument('--db', 921 | action = 'store', 922 | help = 'json object generated by \'opa64.py parse --doc=all\'', 923 | default = '' 924 | ) 925 | 926 | args = ap.parse_args() 927 | if args.func == split_insns: 928 | ret = args.func(args.db) 929 | print(json.dumps(ret)) 930 | exit() 931 | 932 | if args.doc == [] or args.doc[0] == 'all': args.doc = build_doc_list() 933 | if not os.path.exists(args.dir): os.makedirs(args.dir) 934 | 935 | ret = args.func(args.doc, args.dir) 936 | if ret != None: print(json.dumps(ret)) 937 | 938 | # fetch_all() 939 | # insns = parse_all() 940 | # insns = parse_insn_table(to_filepath(urls['table']['a55'], '.')) 941 | # insns = parse_intrinsics(to_filepath(urls['intrinsics'], '.')) 942 | 943 | 944 | 945 | -------------------------------------------------------------------------------- /opv86.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @file opv86.css 4 | * @brief style classes for opv86.js, derived from opv86 (github.com/hikalium/opv86) 5 | * 6 | * @author hikalium (opv86) 7 | * @author Hajime Suzuki (opa64) 8 | * @license MIT 9 | * 10 | * @detail This is a fork of opv86. See opv86 (github.com/hikalium/opv86) for the original source codes. 11 | */ 12 | 13 | body { 14 | font-family: 'arial', sans-serif; 15 | margin-top: 8px; 16 | margin-left: 40px; 17 | margin-right: 40px; 18 | } 19 | .opv86-main-hex-input { 20 | font-family: 'Source Code Pro', monospace; 21 | font-size: 24px; 22 | width: 100%; 23 | border: 0px; 24 | border-bottom: 2px solid #808080; 25 | padding-bottom: 8px; 26 | margin-bottom: 8px; 27 | } 28 | .opv86-main-hex-input:focus { 29 | outline: 0; 30 | } 31 | 32 | h1 small { 33 | color: #808080; 34 | } 35 | a { 36 | color: #1d68cd; 37 | text-decoration: none; 38 | } 39 | a:visited { 40 | color: #1d68cd; 41 | } 42 | 43 | .opv86-checkbox-row { 44 | display: grid; 45 | grid-template-columns: auto max-content 20px max-content; 46 | margin-bottom: 20px; 47 | } 48 | .opv86-checkbox-input { 49 | } 50 | .opv86-data-info { 51 | text-align: right; 52 | margin-bottom: 8px; 53 | } 54 | .opv86-site-info { 55 | text-align: right; 56 | /*margin-bottom: 8px;*/ 57 | } 58 | .opv86-oplist-container { 59 | display: grid; 60 | } 61 | 62 | .opv86-oplist-header { 63 | display: grid; 64 | grid-template-columns: 150px 150px 20px 150px 15px 0.45fr 15px 0.55fr; 65 | background-color: #eeeeee; 66 | border: 0px; 67 | border-bottom: 2px solid #808080; 68 | } 69 | .opv86-header-text { 70 | font-family: 'arial', sans-serif; 71 | font-weight: bold; 72 | white-space: nowrap; 73 | overflow: hidden; 74 | text-overflow: ellipsis; 75 | padding-top: 2px; 76 | padding-bottom: 2px; 77 | padding-left: 2px; 78 | padding-right: 2px; 79 | } 80 | 81 | .opv86-op-container { 82 | display: grid; 83 | background-color: #ffffff; 84 | border: 0px; 85 | border-bottom: 1px solid #bbbbbb; 86 | } 87 | 88 | .opv86-brief-grid { 89 | display: grid; 90 | grid-template-columns: 150px 150px 20px 150px 15px 0.45fr 15px 0.55fr; 91 | } 92 | 93 | .opv86-brief-link { 94 | font-family: 'arial', sans-serif; 95 | font-size: 15px; 96 | padding-top: 1px; 97 | padding-bottom: 3px; 98 | padding-left: 6px; 99 | padding-right: 2px; 100 | } 101 | .opv86-brief-text { 102 | font-family: 'arial', sans-serif; 103 | white-space: nowrap; 104 | overflow: hidden; 105 | text-overflow: ellipsis; 106 | padding-top: 1px; 107 | padding-bottom: 1px; 108 | padding-left: 2px; 109 | padding-right: 2px; 110 | } 111 | .opv86-brief-label { 112 | font-family: 'Source Code Pro', monospace; 113 | white-space: nowrap; 114 | overflow: hidden; 115 | text-overflow: ellipsis; 116 | padding-top: 1px; 117 | padding-bottom: 1px; 118 | padding-left: 2px; 119 | padding-right: 2px; 120 | } 121 | 122 | .opv86-details-container { 123 | display: none; 124 | font-family: 'arial', sans-serif; 125 | padding-top: 10px; 126 | padding-bottom: 10px; 127 | padding-left: 320px; 128 | padding-right: 0px; 129 | } 130 | .opv86-details-section { 131 | font-size: 18px; 132 | padding-top: 5px; 133 | padding-bottom: 5px; 134 | padding-left: 2px; 135 | padding-right: 2px; 136 | } 137 | .opv86-details-body { 138 | display: grid; 139 | font-size: 16px; 140 | padding-top: 6px; 141 | padding-bottom: 6px; 142 | padding-left: 15px; 143 | padding-right: 15px; 144 | } 145 | .opv86-details-pseudocode { 146 | display: grid; 147 | background-color: #eeeeee; 148 | font-family: 'Source Code Pro', monospace; 149 | font-size: 13px; 150 | white-space: break-spaces; 151 | padding-top: 4px; 152 | padding-bottom: 4px; 153 | padding-left: 6px; 154 | padding-right: 6px; 155 | } 156 | 157 | .opv86-synopsis { 158 | display: grid; 159 | grid-template-columns: 150px auto; 160 | } 161 | .opv86-synopsis-tag { 162 | font-weight: bold; 163 | } 164 | .opv86-synopsis-text {} 165 | .opv86-synopsis-label { 166 | font-family: 'Source Code Pro', monospace; 167 | } 168 | 169 | .opv86-table-container { 170 | display: grid; 171 | grid-template-columns: minmax(500px, 1000px) auto; 172 | } 173 | .opv86-table-header { 174 | display: grid; 175 | grid-template-columns: 150px 0.3fr 0.2fr 0.2fr 0.15fr 0.15fr; 176 | background-color: #eeeeee; 177 | border: 0px; 178 | border-bottom: 2px solid #808080; 179 | } 180 | .opv86-table-arch { 181 | display: grid; 182 | grid-template-columns: 150px auto; 183 | border: 0px; 184 | border-bottom: 1px solid #bbbbbb; 185 | } 186 | .opv86-table-variant-container { 187 | display: grid; 188 | background-color: #bbbbbb; 189 | grid-row-gap: 1px; 190 | } 191 | .opv86-table-variant { 192 | display: grid; 193 | grid-template-columns: 0.3fr 0.2fr 0.2fr 0.15fr 0.15fr; 194 | background-color: #ffffff; 195 | } 196 | .opv86-table-text { 197 | padding-top: 1px; 198 | padding-bottom: 1px; 199 | padding-left: 2px; 200 | padding-right: 2px; 201 | } 202 | 203 | .opv86-code-type { 204 | color: #2211aa; 205 | } 206 | .opv86-code-base { 207 | color: #000000; 208 | } 209 | 210 | .opv86-opcode-byte-prefix { 211 | width: 60px; 212 | height: 20px; 213 | display: inline; 214 | text-align: center; 215 | background-color: #c0ffee; 216 | } 217 | .opv86-opcode-byte-modrm { 218 | width: 60px; 219 | height: 20px; 220 | display: inline; 221 | text-align: center; 222 | background-color: #ffeec0; 223 | } 224 | .opv86-opcode-byte-imm8 { 225 | grid-column: span 1; 226 | height: 20px; 227 | display: inline; 228 | text-align: center; 229 | background-color: #eeeeee; 230 | } 231 | .opv86-opcode-byte-imm16 { 232 | grid-column: span 2; 233 | height: 20px; 234 | display: inline; 235 | text-align: center; 236 | background-color: #eeeeee; 237 | } 238 | .opv86-opcode-byte-imm32 { 239 | grid-column: span 4; 240 | height: 20px; 241 | display: inline; 242 | text-align: center; 243 | background-color: #eeeeee; 244 | } 245 | .opv86-opcode-byte-imm64 { 246 | grid-column: span 8; 247 | height: 20px; 248 | display: inline; 249 | text-align: center; 250 | background-color: #eeeeee; 251 | } 252 | .opv86-opcode-byte-normal { 253 | width: 60px; 254 | height: 20px; 255 | display: inline; 256 | text-align: center; 257 | background-color: #eeffc0; 258 | } 259 | .opv86-oplist-item-opcode { 260 | grid-column-start: 1; 261 | background-color: #ffffff; 262 | 263 | display: grid; 264 | grid-template-columns: repeat(10, 60px); 265 | column-gap: 4px; 266 | } 267 | -------------------------------------------------------------------------------- /opv86.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @file opv86.js 3 | * @brief server for opa64 4 | * 5 | * @author hikalium (opv86) 6 | * @author Hajime Suzuki (opa64) 7 | * @license MIT 8 | * 9 | * @detail This is a fork of opv86. See opv86 (github.com/hikalium/opv86) for the original source codes. 10 | */ 11 | var _windowHeight; 12 | var _metadata; 13 | var _original; 14 | var _filtered; 15 | 16 | 17 | function highlightIntl(cls, intr_str) { 18 | var s = $("
").addClass(cls); 19 | if(intr_str.length == 0) { return(s); } 20 | 21 | var parts = intr_str.split(/[(),]+/).filter(function (x) { return(x != ""); }); 22 | var delims = Array(parts.length).fill(', '); 23 | delims[0] = '('; 24 | delims[parts.length - 1] = ');'; 25 | parts.forEach(function (x, i) { 26 | y = parts[i].trim().split(' '); 27 | s.append($("").addClass("opv86-code-type").text(y[0] + ' ')); 28 | s.append($("").addClass("opv86-code-base").text(y[1] + delims[i])); 29 | }); 30 | return(s); 31 | } 32 | 33 | function createIntrLink(cls, txt) { 34 | var s = $("
").addClass(cls); 35 | var pdfpath = _metadata.path.intrinsics + "#page=" + txt; 36 | var fulltxt = "Intrinsics Guide p." + txt; 37 | return(s.append($(`${fulltxt}`))); 38 | } 39 | 40 | function createInsnLink(cls, txt) { 41 | var s = $("
").addClass(cls); 42 | var htmlpath = _metadata.htmldir.description + txt; 43 | return(s.append($(`${txt}`))); 44 | } 45 | 46 | function createMacroLink(cls, txt) { 47 | var s = $("
").addClass(cls); 48 | var pdfpath = _metadata.path.macros + "#page=" + txt.page; 49 | return(s.append($(`${txt.macro.toUpperCase()}`))); 50 | } 51 | 52 | function createText(cls, txt) { 53 | return($("
").addClass(cls).text(txt)); 54 | } 55 | 56 | function extractText(op, tag) { 57 | var txt = op.bf[tag]; 58 | return(Array.isArray(txt) ? txt : [txt]); 59 | } 60 | 61 | function createSynopsisText(op, tag, key) { 62 | var tagToClass = { 63 | "op": "opv86-synopsis-label", 64 | "it": "opv86-synopsis-label", 65 | "as": "opv86-synopsis-label", 66 | "mc": "opv86-synopsis-label", 67 | "eq": "opv86-synopsis-label" 68 | }; 69 | var cls = tag in tagToClass ? tagToClass[tag] : "opv86-synopsis-text"; 70 | 71 | var tagToFn = { 72 | "it": highlightIntl, 73 | "ip": createIntrLink, 74 | "rf": createInsnLink, 75 | "mc": createMacroLink 76 | }; 77 | var fn = tag in tagToFn ? tagToFn[tag] : createText; 78 | 79 | var c = $("
").addClass("opv86-synopsis"); 80 | var arr = extractText(op, tag); 81 | arr.forEach(function (x, i) { 82 | var t = $("
").addClass("opv86-synopsis-tag"); 83 | if(i == 0) { t.text(key); } 84 | c.append(t).append(fn(tagToClass[tag], x)); 85 | }); 86 | return(c); 87 | } 88 | 89 | function createSynopsis(op) { 90 | var h = $("
").addClass("opv86-details-section").text("Synopsis"); 91 | var b = $("
").addClass("opv86-details-body"); 92 | 93 | var tagToKeyName = { 94 | "ic": "Instruction Class", 95 | "ft": "Feature", 96 | "op": "Opcode", 97 | "it": "Intrinsics", 98 | "as": "Assemblies", 99 | "mc": "Feature Macro", 100 | "eq": "Equivalent to", 101 | "cs": "Condition Setting", 102 | "rf": "References", 103 | "ip": "References" 104 | }; 105 | 106 | Object.keys(tagToKeyName).forEach(function (x) { 107 | if(x in op.bf && op.bf[x] != "") { 108 | var key = tagToKeyName[x]; 109 | if(x == "ip" && "rf" in op.bf) { key = ""; } 110 | b.append(createSynopsisText(op, x, key)); 111 | } 112 | }); 113 | return(h.append(b)); 114 | } 115 | 116 | function createDescription(op) { 117 | if(op.ds.dt.length == 0) { return(undefined); } 118 | var s = $("
").addClass("opv86-details-section").text("Description"); 119 | s.append($("
").addClass("opv86-details-body").text(op.ds.dt)); 120 | return(s); 121 | } 122 | 123 | function createOperation(op) { 124 | if(op.ds.or.length == 0) { return(undefined); } 125 | var s = $("
").addClass("opv86-details-section").text("Operation"); 126 | var c = $("
").addClass("opv86-details-body"); 127 | var t = $("
").addClass("opv86-table-container"); 128 | t.append($("
").addClass("opv86-details-pseudocode").text(op.ds.or)); 129 | c.append(t); 130 | s.append(c); 131 | return(s); 132 | } 133 | 134 | function getFullArchName(arch) { 135 | if(arch.startsWith("a")) { return("Cortex-" + arch.toUpperCase()); } 136 | if(arch.startsWith("n")) { return("Neoverse-" + arch.toUpperCase()); } 137 | return(arch); 138 | } 139 | 140 | function createTableReference(arch, page) { 141 | var pdfpath = _metadata.path.table[arch]; 142 | return($("
").addClass("opv86-table-text").append($(`p.${page}`))); 143 | } 144 | 145 | function createTableHeader() { 146 | var h = $("
").addClass("opv86-table-header"); 147 | h.append($("
").addClass("opv86-header-text").text("uArch")); 148 | h.append($("
").addClass("opv86-header-text").text("Variant / Form")); 149 | h.append($("
").addClass("opv86-header-text").text("Latency")); 150 | h.append($("
").addClass("opv86-header-text").text("Throughput")); 151 | h.append($("
").addClass("opv86-header-text").text("Pipes")); 152 | h.append($("
").addClass("opv86-header-text").text("References")); 153 | return(h); 154 | } 155 | 156 | function createTableRow(arch, row) { 157 | var s = $("
").addClass("opv86-table-variant"); 158 | s.append($("
").addClass("opv86-table-text").text(row.vr)); 159 | s.append($("
").addClass("opv86-table-text").text(row.lt)); 160 | s.append($("
").addClass("opv86-table-text").text(row.tp)); 161 | s.append($("
").addClass("opv86-table-text").text(row.ip)); 162 | s.append(createTableReference(arch, row.pp)); 163 | return(s); 164 | } 165 | 166 | function createTableIntl(op) { 167 | var table = $("
"); 168 | table.append(createTableHeader()); 169 | 170 | for(var arch in op.tb) { 171 | var label = $("
").addClass("opv86-table-text").text(getFullArchName(arch)); 172 | var variants = $("
").addClass("opv86-table-variant-container"); 173 | op.tb[arch].forEach(function (r) { variants.append(createTableRow(arch, r)); }); 174 | table.append($("
").addClass("opv86-table-arch").append(label).append(variants)); 175 | } 176 | return(table); 177 | } 178 | 179 | function createTable(op) { 180 | if(Object.keys(op.tb).length == 0) { return(undefined); } 181 | var t = $("
").addClass("opv86-details-section").text("Latency & Throughput"); 182 | t.append($("
").addClass("opv86-details-body").addClass("opv86-table-container").append(createTableIntl(op))); 183 | return(t); 184 | } 185 | 186 | function createDetails(op, id) { 187 | var g = $("
").addClass("opv86-details-container"); 188 | var s = createSynopsis(op); 189 | var d = createDescription(op); 190 | var o = createOperation(op); 191 | var t = createTable(op); 192 | 193 | g.append(s); 194 | if(d !== undefined) { g.append(d); } 195 | if(d !== undefined) { g.append(o); } 196 | if(d !== undefined) { g.append(t); } 197 | return(g); 198 | } 199 | 200 | function createHeader() { 201 | var h = $("
").addClass("opv86-oplist-header"); 202 | h.append($("
").addClass("opv86-header-text").text("Class")); 203 | h.append($("
").addClass("opv86-header-text").text("Feature")); 204 | h.append($("
").addClass("opv86-header-text")); 205 | h.append($("
").addClass("opv86-header-text").text("Opcode")); 206 | h.append($("
").addClass("opv86-header-text")); 207 | h.append($("
").addClass("opv86-header-text").text("Intrinsics")); 208 | h.append($("
").addClass("opv86-header-text")); 209 | h.append($("
").addClass("opv86-header-text").text("Description")); 210 | return(h); 211 | } 212 | 213 | function setupOnClick(s) { 214 | s.click(function(e) { 215 | var p = $(this).parent(); 216 | var d = p.find(".opv86-details-container"); 217 | if(d.length == 0) { 218 | var id = $(this)[0].id; 219 | var op = _filtered[id]; 220 | d = createDetails(op, id); 221 | p.append(d); 222 | } 223 | if(d.css("display") == "none") { 224 | d.slideDown(200); 225 | } else { 226 | d.slideUp(200); 227 | } 228 | }); 229 | return(s); 230 | } 231 | 232 | function findBackgroundColor(op) { 233 | var iclass = op.bf.ic; 234 | var feature = op.bf.ft; 235 | if(feature.startsWith("armv8.1")) { return("#ffd1c2"); } 236 | if(feature.startsWith("armv8.2")) { return("#ffc2c2"); } 237 | if(feature.startsWith("armv8.3")) { return("#ffc2e0"); } 238 | if(feature.startsWith("armv8.4")) { return("#ffc2ff"); } 239 | if(feature.startsWith("armv8.5")) { return("#e0c2ff"); } 240 | if(feature.startsWith("armv8.6")) { return("#c2c2ff"); } 241 | 242 | if(iclass == "general") { return("#ffffc2"); } 243 | if(iclass == "advsimd") { return("#ffeec0"); } 244 | if(iclass == "float") { return("#e0ffc2"); } 245 | if(iclass == "fpsimd") { return("#c2ffc2"); } 246 | if(iclass == "sve") { return("#c2f0ff"); } 247 | return("#cccccc"); 248 | } 249 | 250 | function createBrief(op, id) { 251 | var c = { "background-color": findBackgroundColor(op) }; 252 | var s = $("
").addClass("opv86-brief-grid").attr({ "id": id }); 253 | 254 | s.append($("
").addClass("opv86-brief-text").text(op.bf.ic).css(c)); 255 | s.append($("
").addClass("opv86-brief-text").text(op.bf.ft).css(c)); 256 | s.append($("
").addClass("opv86-brief-text")); 257 | s.append($("
").addClass("opv86-brief-label").text(op.bf.op)); 258 | s.append($("
").addClass("opv86-brief-text")); 259 | s.append(highlightIntl("opv86-brief-label", op.bf.it)); 260 | s.append($("
").addClass("opv86-brief-text")); 261 | s.append($("
").addClass("opv86-brief-text").text(op.ds.bf)); 262 | return(setupOnClick(s)); 263 | } 264 | 265 | 266 | function extendOplist(oplist, data, from, to) { 267 | if(to > data.length) { to = data.length; } 268 | for(var i = from; i < to; i++) { 269 | var op = data[i]; 270 | var s = createBrief(op, i); 271 | var c = $("
").addClass("opv86-op-container").append(s); 272 | oplist.append(c); 273 | } 274 | } 275 | 276 | function updateHeight () { 277 | _windowHeight = $(window).height(); 278 | } 279 | 280 | function extendOnScroll() { 281 | if($(window).scrollTop() > ($(document).height() - 2 * _windowHeight)) { 282 | var oplist = $("#oplist"); 283 | var data = _filtered; 284 | var start = oplist[0].childNodes.length; 285 | extendOplist(oplist, data, start, start + 50); 286 | } 287 | } 288 | 289 | function filterClass(op, cls) { 290 | if(cls.includes("intrinsics-only") && op.bf.it == "") { return(false); } 291 | if(cls.includes("general-only") && op.bf.ic != "general") { return(false); } 292 | if(!cls.includes("include-sve") && op.bf.ic == "sve") { return(false); } 293 | if(!cls.includes("include-system") && op.bf.ic == "system") { return(false); } 294 | return(true); 295 | } 296 | 297 | function findKey(op, filter_word) { 298 | var keys = ["ic", "ft", "op", "it"]; 299 | for(var k of keys) { if(op.bf[k].indexOf(filter_word) != -1) { return(true); } } 300 | 301 | var keys_opt = ["as"]; 302 | for(var k of keys_opt) { if(k in op.bf && op.bf[k].indexOf(filter_word) != -1) { return(true); } } 303 | 304 | if(op.ds.bf.toLowerCase().indexOf(filter_word) != -1) { return(true); } 305 | if(op.ds.dt.toLowerCase().indexOf(filter_word) != -1) { return(true); } 306 | return(false); 307 | } 308 | 309 | function rebuildOplist() { 310 | var oplist = $("#oplist"); 311 | oplist.empty(); 312 | oplist.append(createHeader()); 313 | 314 | var clskeys = ["intrinsics-only", "general-only", "include-sve", "include-system"]; 315 | var filter_cls = clskeys.filter(function (x) { return($("#" + x).is(':checked')); }); 316 | var filter_key = $("#filter-value").val().toLowerCase(); 317 | 318 | _filtered = _original.filter(function (x) { return(filterClass(x, filter_cls) && findKey(x, filter_key)); }) 319 | 320 | var num_recs = ($(window).height() / 30) * 5; 321 | extendOplist(oplist, _filtered, 0, num_recs); 322 | } 323 | 324 | function initOplist(data) { 325 | $("#filter-value").val(""); 326 | _metadata = data.metadata; 327 | _original = data.insns; 328 | _windowHeight = $(window).height(); 329 | rebuildOplist(); 330 | } 331 | 332 | $.getJSON(`./data/db.json`, function(data) { 333 | initOplist(data); 334 | 335 | $("#filter-checkbox").change(function () { rebuildOplist(); }); 336 | $("#filter-value").keyup(function () { rebuildOplist(); }); 337 | $(window).resize(updateHeight); 338 | $(window).scroll(extendOnScroll); 339 | }); 340 | 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ocxtal/opa64/eab48e49e7a20835e84f378b46f93035a3313895/screenshot.png --------------------------------------------------------------------------------