├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── SPEC.md ├── demo ├── channel │ ├── Makefile │ ├── README.md │ ├── channel.flat_js │ ├── channel.js │ ├── intqueue.flat_js │ ├── intqueue.js │ ├── test-sendmsg-master.js │ ├── test-sendmsg-worker.js │ └── test-sendmsg.html ├── ray-flatjs │ ├── README.md │ ├── expected-output.ppm │ ├── ray.flat_js │ └── ray.html ├── ray-simd │ ├── README.md │ ├── expected-output.ppm │ ├── ray.html │ └── ray.js └── ray │ ├── README.md │ ├── expected-output.ppm │ ├── ray.html │ └── ray.js ├── fjsc.js ├── fjsc.ts ├── libflatjs.js ├── test ├── Makefile ├── atomic-tests.flat_js ├── basic-tests.flat_js ├── simd-tests.flat_js ├── synchronic-tests.flat_js └── typescript-tests.flat_ts ├── tsd.json └── typings ├── node └── node.d.ts └── tsd.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | 364 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: .ts .js 2 | 3 | fjsc.js: fjsc.ts 4 | tsc -t ES5 -m commonjs --noImplicitAny --suppressImplicitAnyIndexErrors fjsc.ts 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlatJS 2 | 3 | FlatJS is a "language fragment" layered on JavaScript that allows programs to use flat memory -- ArrayBuffer and SharedArrayBuffer -- conveniently with high performance. 4 | 5 | FlatJS provides structs, classes, and arrays within flat memory, as well as atomic and synchronic fields when using shared memory. There is single inheritance among classes and class instances carry virtual methods. 6 | 7 | Objects in flat memory are manually managed and represented in JavaScript as pointers into the flat memory, not as native JavaScript objects. However, a second layer provides for simple, optional reification of objects in flat memory as true JavaScript objects. 8 | 9 | FlatJS is implemented as a preprocessor that translates JavaScript+FlatJS into plain JavaScript. JavaScript dialects, such as TypeScript, are supported. 10 | 11 | See the wiki for tutorials and notes on future evolution; see SPEC.md for a language specification. 12 | 13 | See test/ and demo/ for complete test programs. 14 | -------------------------------------------------------------------------------- /SPEC.md: -------------------------------------------------------------------------------- 1 | # FlatJS 2 | 3 | ## Introduction 4 | 5 | FlatJS is a "language fragment" layered on JavaScript (and JS dialects) 6 | that allows programs to use flat memory (ArrayBuffer and SharedArrayBuffer) 7 | conveniently with good performance. 8 | 9 | FlatJS provides structs, classes, and arrays within flat memory, as 10 | well as atomic and synchronic fields when using shared memory. There 11 | is support for SIMD values. Objects in flat memory are manually 12 | managed and normally represented in JavaScript as pointers into the 13 | shared memory (ie, integer addresses), not as native JavaScript 14 | objects. Virtual methods are provided for on class instances. 15 | 16 | FlatJS is a static language and is implemented as a preprocessor that 17 | translates JavaScript+FlatJS into plain JavaScript. 18 | 19 | A slightly more dynamic layer sits on top of the static layer, 20 | allowing objects in flat memory to be exposed as JavaScript classes 21 | within a single JavaScript context. 22 | 23 | 24 | ## Caveats 25 | 26 | For ease of processing *only*, the syntax is currently line-oriented: 27 | Line breaks are explicit in the grammars below and some ```@``` characters 28 | appear here and there to make recognition easier. Please don't get 29 | hung up on this, it's mostly a matter of programming to fix it but that 30 | is not my focus right now. 31 | 32 | The translator is implemented by means of a set of regular expression 33 | matchers and an unforgiving, context-insensitive, nonhygienic macro 34 | expander. Occasionally this leads to problems. Some things to watch 35 | out for: 36 | 37 | * Treat all operation names (get, set, at, setAt, ref, add, etc, see below) 38 | as reserved words - do not name your fields with those identifiers. 39 | * Do not use expressions containing template strings or regular expressions 40 | in the arguments to accessor macros (including array accessors). 41 | * Do not split calls to accessors across multiple source lines, because 42 | frequently the translator must scan for the end of the call 43 | * If using the assignment shorthand, keep the right-hand-side entirely 44 | on the same line as the assignment operator. 45 | * Occasionally, the translator fails to parenthesize code correctly because 46 | it can't insert parentheses blindly as that interacts badly with 47 | automatic semicolon insertion (yay JavaScript). When in doubt, use 48 | semicolons. 49 | 50 | Failures to obey these rules will sometimes lead to mysterious parsing 51 | and runtime errors. A look at the generated code is usually enough to 52 | figure out what's going on; problems tend to be local. 53 | 54 | It's a goal to make the macro substitution more reliable. 55 | 56 | 57 | ## Programs 58 | 59 | A program comprises a set of files that are processed together by the FlatJS 60 | compiler. In a Web context, all the files loaded into a tab, or all the files 61 | loaded into a worker, would normally be processed together. 62 | 63 | 64 | ## Types 65 | 66 | There is a program-wide namespace for FlatJS types. This namespace contains 67 | the predefined primitive types and every user-defined (struct or class) type. 68 | Type names in a program must be unique in this namespace. 69 | 70 | Every type that is referenced from an annotation or as a base type must be 71 | defined in the set of files processed together. Types can be defined in any 72 | order and in any file of the program. 73 | 74 | Within the bodies of methods, 'this' has an undetermined binding (for now) 75 | and should not be referenced. 76 | 77 | 78 | ## Primitive types 79 | 80 | There are predefined global type objects with the following names: *int8*, *uint8*, 81 | *int16*, *uint16*, *int32*, *uint32*, *float32*, *float64*, *int32x4*, 82 | *float32x4*, and *float64x2*. 83 | 84 | Each predefined type object T has five properties: 85 | 86 | * T.NAME is the name of the type (a string) 87 | * T.SIZE is the size in bytes of the type 88 | * T.ALIGN is the required alignment for the type 89 | * T.get(self) => value of a cell of the type 90 | * T.set(self, v) => set the value of a cell of the type 91 | 92 | ## Struct types 93 | 94 | A struct describes a value type (instances do not have object identity) 95 | with named, mutable fields. 96 | 97 | ### Syntax 98 | 99 | ``` 100 | Struct-def ::= (lookbehind EOL) 101 | "@flatjs" "struct" Id "{" Comment? EOL 102 | ((Comment | Field) EOL)* 103 | ((Comment | Struct-Method) EOL)* 104 | "}" "@end" Comment? EOL 105 | 106 | Field ::= Ident ":" Type ";"? Comment? EOL 107 | 108 | Comment ::= "//" Not-EOL* 109 | 110 | Type ::= ValType | ArrayType 111 | ValType ::= ("int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32") (".atomic" | ".synchronic")? 112 | | "float32" | "float64" 113 | | "int32x4" | "float32x4" | "float64x2" 114 | | Id 115 | ArrayType ::= ValType ".Array" 116 | 117 | Struct-Method ::= "@get" "(" "SELF" ")" Function-body 118 | | "@set" "(" "SELF" ("," Parameter)* ("," "..." Id)? ")" Function-body 119 | 120 | Parameter ::= Id (":" Tokens-except-comma-or-rightparen )? 121 | 122 | Id ::= [A-Za-z_][A-Za-z0-9_]* 123 | ``` 124 | 125 | Note the following: 126 | 127 | * The annotation on the Parameter is not used by FlatJS, but is allowed in order 128 | to interoperate with TypeScript. 129 | * The restriction of properties before methods is a matter of economizing on the 130 | markup; as it is, properties don't need eg ```@var``` before them. This restriction 131 | can be lifted once we have a better parser, see Issue #11. 132 | 133 | 134 | ### Static semantics 135 | 136 | Every field name in a struct must be unique within that struct. 137 | 138 | A field of struct type gives rise to a named substructure within the 139 | outer structure that contains the fields of the nested struct. No struct 140 | may in this way include itself. 141 | 142 | 143 | ### Dynamic semantics 144 | 145 | Within the body of a method, "this" denotes the type object carrying 146 | the method. 147 | 148 | ### Translation 149 | 150 | For a struct type named R, with field names F1 .. Fn, the following 151 | will be defined, where "self" denotes a memory offset properly aligned 152 | for R and with a value such that memory offset self+R.size-1 is within 153 | the memory. 154 | 155 | 156 | #### Global value properties 157 | 158 | R is a global variable holding a function object designating the type. 159 | See "JavaScript front objects", later. 160 | 161 | R.SIZE is the size in bytes of R, rounded up such that an array of R 162 | structures can be traversed by adding R.SIZE to a pointer to one 163 | element of the array to get to the next element, and allocating 164 | n*R.SIZE will allocate space enough to hold n such elements. 165 | 166 | R.ALIGN is the required alignment for R, in bytes. 167 | 168 | R.NAME is the name of the type R. 169 | 170 | 171 | #### Global function properties 172 | 173 | Whole-type accessors: 174 | 175 | * R.get(self) => reified value of the structure, 176 | * R.set(self, v) => set the value of the structure from a reified value 177 | 178 | Field accessors for all field types: 179 | 180 | * R.Fk(self) => Shorthand for R.fk.get(self) 181 | * R.Fk.get(self) => value of self.Fk field 182 | If the field is a structure then the getter reifies the structure as a JavaScript object. 183 | If the field type T does not have a ```@get``` method then the getter returns a new instance 184 | of the JavaScript type R, with properties whose values are the values 185 | extracted from the flat object, with standard getters. If T does have 186 | a ```@get``` method then R.Fk(self) invokes that method on SELF.Fk.ref and returns its 187 | result. 188 | * R.Fk.set(self, v) => void; set value of self.Fk field to v. 189 | If the field is a structure then the setter is a function that updates the shared object from ```v```. 190 | If the field type T does not have a ```@set``` method then this method will set each field 191 | of self.Fk in order from same-named properties extracted from ```value```. 192 | If T does have a ```@set``` method then this method will invoke that method 193 | on SELF.Fk.ref and ```value```. 194 | * R.Fk.ref(self, v) => reference to the self.Fk field 195 | 196 | If a field Fk is designated "atomic" then the getter and setter just 197 | shown use atomic loads and stores. In addition, the following atomic 198 | functions are defined: 199 | 200 | * R.Fk.compareExchange(self, o, n) => if the value of self.Fk field is o then store n; return old value 201 | * R.Fk.add(self, v) => add v to value of self.Fk field; return old value 202 | * R.Fk.sub(self, v) => subtract v from value of self.Fk field; return old value 203 | * R.Fk.and(self, v) => and v into value of self.Fk field; return old value 204 | * R.Fk.or(self, v) => or v into value of self.Fk field; return old value 205 | * R.Fk.xor(self, v) => xor v to value of self.Fk field; return old value 206 | 207 | If a field Fk is designated "synchronic" then the setter and atomics 208 | just shown are synchronic-aware (every update sends a notification). 209 | In addition, the following synchronic functions are defined: 210 | 211 | * R.Fk.expectUpdate(self, v, t) => void; wait until the value of the 212 | self.Fk field is observed not to hold v, or until t milliseconds have passed 213 | * R.Fk.loadWhenEqual(self, v) => wait until the value of the self.Fk 214 | field is observed to hold v, then return the value of that field 215 | (which might have changed since it was observed to hold v) 216 | * R.loadWhenNotEqual_Fk(self, v) => wait until the value of the self.Fk 217 | field is observed not to hold v, then return the value of that field 218 | (which might have changed back to v since it was observed) 219 | * R.Fk.notify(self) => wake all waiters on self.Fk, making them re-check 220 | their conditions. 221 | 222 | If a field Fk has a struct type T with fields G1 .. Gm then the 223 | following functions are defined: 224 | 225 | * Getters, setters, and accessors for fields G1 through Gm within Fk, 226 | with the general pattern R.Fk.Gi(self) and R.Fk.Gi.op(self,...), by 227 | the rules above. 228 | 229 | 230 | ## Class types 231 | 232 | A class describes a reference type (instances have object identity) 233 | with mutable fields. 234 | 235 | ### Syntax 236 | 237 | A class definition takes the form of a number of fields followed 238 | by a number of methods: 239 | 240 | ``` 241 | Class-def ::= (lookbehind EOL) 242 | "@flatjs" "class" Id ("extends" Id)? "{" Comment? EOL 243 | ((Comment | Field) EOL)* 244 | ((Comment | Class-Method) EOL)* 245 | "}" "@end" Comment? EOL 246 | 247 | Class-method ::= ("@method"|"@virtual") Id "(" "SELF" ("," Parameter)* ("," "..." Id)? ")" Function-body 248 | ``` 249 | 250 | ### Static semantics 251 | 252 | If a class definition contains an extends clause then the Id in 253 | the extends clause must name a class (the "base class"). 254 | No class may extend itself directly or indirectly. 255 | 256 | No field within a class definition must have a name that 257 | matches any other field or method within the class definition 258 | or within the definition of any base class. 259 | 260 | No method within a class definition must have a name that matches any 261 | method within the class definition. However, a method in a class may 262 | match the name of a method in the base class if they are either both 263 | designated virtual or if neither is designated virtual. 264 | 265 | (The case for neither being designated virtual is to accomodate the 266 | ```init``` method. I suspect that a special-case rule for ```init``` 267 | may be better, but I'm not sure yet.) 268 | 269 | A virtual method whose name matches the name of a method in a base 270 | class is said to override the base class method. Overriding methods 271 | must have the same signature (number and types of arguments) as the 272 | overridden method. 273 | 274 | The first argument to a method is always the keyword SELF. 275 | 276 | As for structs, a field of struct type gives rise to a named substructure 277 | within the outer class that contains the fields of the nested struct. 278 | 279 | A field of class type gives rise to a pointer field. 280 | 281 | The layout of a class is compatible with the layout of its base class: 282 | any accessor on a field of a base class, and any invoker of a method 283 | defined on the base class can be used on an instance of the derived 284 | class. 285 | 286 | 287 | ### Dynamic semantics 288 | 289 | Before memory for a class instance can be used as a class instance, 290 | the class's initInstance method must be invoked on the memory. 291 | If used, the ```@new``` operator (see below) invokes initInstance 292 | on behalf of the program. 293 | 294 | Within the body of a method, "this" denotes the type object carrying 295 | the method. (Thus the calls ```SELF.method(...)``` and 296 | ```this.method(SELF,...)``` are equivalent.) 297 | 298 | 299 | ### Translation 300 | 301 | For a class type named C, with field names F1 .. Fn and method names M1 .. Mn 302 | (including inherited fields and methods), the following 303 | will be defined, where "self" denotes a memory offset properly aligned 304 | for C and with a value such that memory offset self+C.size-1 is within 305 | the memory. 306 | 307 | 308 | #### Global value properties 309 | 310 | C is a global variable holding a function object designating the type. 311 | See "JavaScript front objects", later. 312 | 313 | C.SIZE is the size in bytes of C. Note that since C is a reference 314 | type, to allocate an "array of C" means to allocate an array of 315 | pointers, which are int32 values. 316 | 317 | C.ALIGN is the required alignment for C, in bytes. 318 | 319 | C.NAME is the name of the type C. 320 | 321 | C.CLSID for the type ID for the type. 322 | 323 | C.BASE for the base type of R, or null. 324 | 325 | C.prototype is an instance of the function object designating the base 326 | type, if there is a base type, otherwise an Object. 327 | 328 | NOTE, C.get() and C.set() are not defined, those are defined only on 329 | value types. 330 | 331 | 332 | #### Global function properties 333 | 334 | Getters and setters for fields are translated exactly for structs. 335 | If class D derives from class B and class B has a field x, then 336 | there will be accessors for x defined on D as well. 337 | 338 | If a method Mk is defined on class C or is a virtual method inherited 339 | from class B, then an invoker is defined for Mk on C: 340 | 341 | * C.Mk(self, ...) 342 | 343 | The invoker for a virtual method will determine the actual type of 344 | self and invoke the correct method implementation; the invoker for a 345 | direct method will just contain the method implementation. 346 | 347 | self must reference an instance of type C or a subclass of C. The 348 | type is determined by consulting a hidden field within the instance 349 | that contains the class ID of the object. 350 | 351 | If a virtual method Mk is defined on class C, then an implementation 352 | is defined for Mk on C: 353 | 354 | * C.Mk_impl(self, ...) 355 | 356 | The implementation is the actual method body defined within the class. 357 | 358 | 359 | ## Array types 360 | 361 | FlatJS arrays are primitive reference types that do /not/ carry their 362 | length: they are simply a sequence of elements of a given type within 363 | memory. 364 | 365 | Allocating an array of primitive or structure type T of length n requires only allocating 366 | memory for n*T.SIZE. 367 | 368 | Allocating an array of class type T of length n requires only allocating 369 | memory for n*int32.SIZE. 370 | 371 | ### Translation 372 | 373 | Suppose A is some type. 374 | 375 | * A.Array.at(ptr, i) reads the ith element of an array of A 376 | whose base address is ptr. Not bounds checked. If the base 377 | type of the array is a structure type this will only work if 378 | the type has a ```@get``` method. 379 | * A.Array.setAt(ptr, i, v) writes the ith element of an array of A 380 | whose base address is ptr. Not bounds checked. If the base 381 | type of the array is a structure type this will only work if 382 | the type has an ```@set``` method. 383 | * A.Array.ref(ptr, i) returns a reference to the ith element. 384 | * If the base type A is a structure type then the path to a field 385 | within the structure can be denoted: A.Array.x.y.at(ptr, i) 386 | returns the x.y field of the ith element of the array ptr. 387 | Ditto for setAt. 388 | 389 | NOTE: Arrays of atomics and synchronics, and operations on those, will appear. 390 | 391 | 392 | ## ```@new``` macro 393 | 394 | An instance of the class type may be allocated and initialized with 395 | the operator-like ```@new``` macro. Specifically, ```@new T``` for FlatJS class 396 | type T expands into this: 397 | ``` 398 | T.initInstance(FlatJS.allocOrThrow(T.SIZE, T.ALIGN)) 399 | ``` 400 | 401 | An array may also be allocated and initialized with ```@new```. 402 | Specifically, ```@new T.Array(n)``` for type T expands into 403 | this: 404 | ``` 405 | FlatJS.allocOrThrow(n*, ) 406 | ``` 407 | where *size* is int32.SIZE if T is a reference type or T.SIZE 408 | otherwise, and *align* is int32.ALIGN if T is a reference type 409 | and T.ALIGN otherwise. 410 | 411 | Value types may be allocated with ```@new```, and are 412 | default-initialized (all zero bits). ```@new int32``` and ```@new T``` for 413 | some struct type T expand to this code: 414 | ``` 415 | FlatJS.allocOrThrow(int32.SIZE, int32.ALIGN) 416 | FlatJS.allocOrThrow(T.SIZE, T.ALIGN); 417 | ``` 418 | 419 | NOTE: Arrays of atomics and synchronics, and operations on those, will appear. 420 | 421 | ## SELF accessor macros 422 | 423 | Inside the method for a struct or class T, the identifier SELF acts as 424 | a keyword identifying the object pointer that is the target of the 425 | method. 426 | 427 | Within the method, a number of macros are tied to the syntax "SELF.", 428 | as follows. 429 | 430 | If the type T has a suite of accessor methods F1..Fn, there will be 431 | macros SELF.F1..SELF.Fk. A reference to SELF.Fj(arg,...) is rewritten 432 | at translation time as a reference to T.Fj(SELF, arg, ...). 433 | 434 | In the special case of a field getter Fg, which takes only the SELF 435 | argument, the form of the macro invocation shall be SELF.Fj, that is, 436 | without the empty parameter list. This is rewritten as T.Fj(SELF). 437 | 438 | If a macro invocation has the form SELF.Fj op expr, where op is one of 439 | the assignment operators =, +=, -=, &=, |=, and ^=, then the macro 440 | invocation is rewritten as T.operation_Fj(SELF, expr), where operation 441 | is "set", "add", "sub", "and", "or", and "xor", respectively. 442 | 443 | The plain assignment operator can be chained, eg, SELF.x=SELF.y=0, but 444 | not for SIMD field types (at present). 445 | 446 | Furthermore, if there exists a method Mg and the syntax that is being 447 | used is SELF.Mg(arg, ...) then this is rewritten as T.Mg(SELF,arg,...). 448 | 449 | 450 | ## Global macros 451 | 452 | All field accessors to simple fields are macro-expanded at translation time. 453 | 454 | Note carefully that the field accessors that are macro-expanded are not, 455 | in fact, available as properties on the type objects at run-time. 456 | 457 | 458 | ## JavaScript front objects 459 | 460 | The objects that describe FlatJS types are also JavaScript functions, 461 | that is, standard JS class types. 462 | 463 | These JS types are used in situations where JavaScript objects act as 464 | front objects or proxies for flat objects, as described next. 465 | 466 | Going via the front objects will generally be slower than going 467 | directly to the flat objects, but provide a better interface to 468 | JavaScript programs. 469 | 470 | 471 | ### Struct front objects 472 | 473 | If a struct descriptor object is invoked as a function, it does 474 | nothing and returns nothing. If it is invoked as a constructor, it 475 | returns a completely empty JavaScript object of that JS type. 476 | 477 | The default struct getter will return a JavaScript object that is an 478 | instance of the struct's descriptor object, with fields named by the 479 | fields of the struct, and field values extracted from the struct. The 480 | JS object does not reference the shared struct in any way. 481 | 482 | ### Class front objects 483 | 484 | If a class descriptor object is invoked as a function, it does nothing 485 | and returns nothing. If it is invoked as a constructor, it should be 486 | passed one argument (defaults to NULL), which must be a pointer to a 487 | shared object of the type denoted by the descriptor or one of its 488 | subtypes. 489 | 490 | The JS front object for a class has a getter called ```pointer``` that 491 | extracts the pointer stored in the object. There are no other 492 | prototype methods, and there are no restrictions on what methods can 493 | be stored in the prototype. 494 | 495 | NOTE: It may be that we want to automatically create proxy methods on 496 | the prototype for (some) shared object methods and fields. 497 | 498 | NOTE: As the constructor for the front object already has a 499 | definition, any construction protocols for them that pass additional 500 | arguments will have to be implemented via a static constructor. This 501 | is a weakness of the system. (It's fixable, and may be fixed.) 502 | 503 | The front object for a FlatJS class normally holds no values of its 504 | own except the pointer to the shared object. 505 | 506 | If there is a FlatJS class D that has a base class B, then the D 507 | function's prototype property holds an empty instance of B, that is, 508 | the "instanceof" operator will function correctly on front objects. 509 | 510 | 511 | ## Environment 512 | 513 | There is a new global object called "FlatJS". This is defined in 514 | libflatjs.js, which must be loaded once before application files that 515 | are translated from FlatJS. 516 | 517 | For each primitive type (int8, uint8, int16, uint16, int32, uint32, 518 | float32, float64, int32x4, float32x4, float64x2) there is a global 519 | variable with the type name. 520 | 521 | There is a global variable called NULL whose value is the null 522 | pointer, whose integer value is zero. 523 | 524 | The FlatJS object has the following public methods: 525 | 526 | * init(buffer [, initialize]) takes an ArrayBuffer or 527 | SharedArrayBuffer and installs it as the global heap. The buffer 528 | must be cleared to all zeroes before being used, and client code 529 | should assume it may not modify it directly after calling init() on 530 | it. If initialize=true then the memory is also appropriately 531 | initialized. Initialize must be true in the first call to init(), 532 | and the call that performs initialization must return before any 533 | other calls to init() are made. [Normally this means you 534 | init(...,true) on the main thread before you send a 535 | SharedArrayBuffer to other workers.] 536 | * alloc(numBytes, byteAlignment) allocates and zero-initializes an 537 | object of size numBytes with alignment at least byteAlignment and 538 | returns it. If the allocation fails then returns the NULL pointer. 539 | * allocOrThrow(numBytes, byteAlignment) allocates and zero-initializes 540 | an object of size numBytes with alignment at least byteAlignment and 541 | returns it. If the allocation fails then throws a MemoryError 542 | exception. 543 | * free(p) frees an object p that was obtained from alloc(), or does 544 | nothing if p is the NULL pointer. 545 | * identify(p) returns the Class object if p is a pointer to a class 546 | instance, or null. 547 | 548 | The allocator operates on the flat memory and is thread-safe if that 549 | memory is shared. 550 | 551 | There is a new global constructor called MemoryError, derived from 552 | Error. It is thrown by allocOrThrow if there is not enough memory to 553 | fulfill the request. 554 | 555 | ## How to run the translator 556 | 557 | The translator has to be run on all files in the program 558 | simultaneously, and will resolve all types in all files. It will 559 | output a file for each input file. 560 | 561 | ## Type IDs 562 | 563 | Type IDs must be unique and invariant across workers, which are all 564 | running different programs. 565 | 566 | A type ID is the hash value of a string representing the name of a 567 | type. 568 | 569 | NOTE: If a program has two types with the same type ID then the program 570 | cannot be translated. A workaround is to change the name of one of 571 | the conflicting types. If this turns out to be a constant problem we 572 | can introduce a notion of a "brand" on a type which is just a fudge 573 | factor to make the type IDs work out. (This scales poorly but is 574 | probably OK in practice.) 575 | -------------------------------------------------------------------------------- /demo/channel/Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: .js .flat_js 2 | 3 | .PHONY: all 4 | all: 5 | nodejs ../../fjsc.js channel.flat_js intqueue.flat_js 6 | -------------------------------------------------------------------------------- /demo/channel/README.md: -------------------------------------------------------------------------------- 1 | # Channel demo 2 | 3 | This has been adapted from the channel testcase in parlib-simple to 4 | show off flatjs when applied to the intqueue data structure with its 5 | built-in synchronics. 6 | 7 | The demo just ping-pongs a marshaled message between main and the 8 | worker, in shared memory. 9 | -------------------------------------------------------------------------------- /demo/channel/channel.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 | 7 | /* 8 | * Simple unidirectional marshaling shared-memory channel. There can 9 | * be multiple senders and multiple receivers. 10 | * 11 | * This is by and large like postMessage() except that it cannot 12 | * transfer ArrayBuffer values (it can only copy them), and it cannot 13 | * send or receive SharedArrayBuffer values at all. Also, the 14 | * marshaler currently does not deal with circular/shared structure 15 | * but that's fixable. 16 | */ 17 | 18 | // REQUIRE: 19 | // marshaler.js (from parlib-simple) 20 | // intqueue.js (from this directory) 21 | 22 | "use strict"; 23 | 24 | /* 25 | * Create shared state for the channel. The state is opaque. The 26 | * returned value can be sent among workers with postMessage and 27 | * passed to the constructors for ChannelSender and ChannelReceiver. 28 | * 29 | * "size" is the capacity of the underlying message queue, in bytes. 30 | * 31 | * How much space will you need? The channel transmits a stream of 32 | * tag+value pairs, or fieldname+tag+value triples in objects. It 33 | * optimizes transmission of typed data structures (strings, 34 | * TypedArrays) by omitting tags when it can. If mostly small data 35 | * structures are being sent then a few kilobytes should be enough to 36 | * allow a number of messages to sit in a queue at once. 37 | */ 38 | function makeChannelSharedState(size) { 39 | return IntQueue.init(@new IntQueue, Math.floor(size/4)); 40 | } 41 | 42 | /* 43 | * Create a sender endpoint of the channel. 44 | * 45 | * "shared_state" is a data structure created with makeChannelSharedState. 46 | */ 47 | function ChannelSender(shared_state) { 48 | this._queue = shared_state; 49 | this._marshaler = new Marshaler(); 50 | } 51 | 52 | /* 53 | * Send a message on the channel, waiting for up to t milliseconds for 54 | * available space (undefined == indefinite wait), and then return 55 | * without waiting for the recipient to pick up the message. 56 | * 57 | * Returns true if the message was sent, false if space did not become 58 | * available. 59 | * 60 | * Throws ChannelEncodingError on encoding error. 61 | */ 62 | ChannelSender.prototype.send = function(msg, t) { 63 | try { 64 | var {values, newSAB} = this._marshaler.marshal([msg]); 65 | } 66 | catch (e) { 67 | // TODO: This could be improved by making the Marshaler throw useful errors. 68 | throw new ChannelEncodingError("Marshaler failed:\n" + e); 69 | } 70 | if (newSAB.length) 71 | throw new ChannelEncodingError("SharedArrayBuffer not supported"); 72 | return IntQueue.enqueue(this._queue, values, t); 73 | } 74 | 75 | /* 76 | * Create a receiver endpoint. See comments on the sender endpoint. 77 | */ 78 | function ChannelReceiver(shared_state) { 79 | this._queue = shared_state; 80 | this._marshaler = new Marshaler(); 81 | } 82 | 83 | /* 84 | * Receive a message from the channel, waiting for up to t 85 | * milliseconds (undefined == indefinite wait) until there is a 86 | * message if necessary. Returns the message, or the noMessage value 87 | * if none was received. 88 | */ 89 | ChannelReceiver.prototype.receive = function (t, noMessage) { 90 | var M = IntQueue.dequeue(this._queue, t); 91 | if (M == null) 92 | return noMessage; 93 | return this._marshaler.unmarshal(M, 0, M.length)[0]; 94 | } 95 | 96 | /* 97 | * Error object. 98 | */ 99 | function ChannelEncodingError(message) { 100 | this.message = message; 101 | } 102 | ChannelEncodingError.prototype = new Error; 103 | -------------------------------------------------------------------------------- /demo/channel/channel.js: -------------------------------------------------------------------------------- 1 | // Generated from channel.flat_js by fjsc 0.5; github.com/lars-t-hansen/flatjs 2 | /* -*- mode: javascript -*- */ 3 | 4 | /* This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 | 8 | /* 9 | * Simple unidirectional marshaling shared-memory channel. There can 10 | * be multiple senders and multiple receivers. 11 | * 12 | * This is by and large like postMessage() except that it cannot 13 | * transfer ArrayBuffer values (it can only copy them), and it cannot 14 | * send or receive SharedArrayBuffer values at all. Also, the 15 | * marshaler currently does not deal with circular/shared structure 16 | * but that's fixable. 17 | */ 18 | 19 | // REQUIRE: 20 | // marshaler.js (from parlib-simple) 21 | // intqueue.js (from this directory) 22 | 23 | "use strict"; 24 | 25 | /* 26 | * Create shared state for the channel. The state is opaque. The 27 | * returned value can be sent among workers with postMessage and 28 | * passed to the constructors for ChannelSender and ChannelReceiver. 29 | * 30 | * "size" is the capacity of the underlying message queue, in bytes. 31 | * 32 | * How much space will you need? The channel transmits a stream of 33 | * tag+value pairs, or fieldname+tag+value triples in objects. It 34 | * optimizes transmission of typed data structures (strings, 35 | * TypedArrays) by omitting tags when it can. If mostly small data 36 | * structures are being sent then a few kilobytes should be enough to 37 | * allow a number of messages to sit in a queue at once. 38 | */ 39 | function makeChannelSharedState(size) { 40 | return IntQueue.init(IntQueue.initInstance(FlatJS.allocOrThrow(68,4)), Math.floor(size/4)); 41 | } 42 | 43 | /* 44 | * Create a sender endpoint of the channel. 45 | * 46 | * "shared_state" is a data structure created with makeChannelSharedState. 47 | */ 48 | function ChannelSender(shared_state) { 49 | this._queue = shared_state; 50 | this._marshaler = new Marshaler(); 51 | } 52 | 53 | /* 54 | * Send a message on the channel, waiting for up to t milliseconds for 55 | * available space (undefined == indefinite wait), and then return 56 | * without waiting for the recipient to pick up the message. 57 | * 58 | * Returns true if the message was sent, false if space did not become 59 | * available. 60 | * 61 | * Throws ChannelEncodingError on encoding error. 62 | */ 63 | ChannelSender.prototype.send = function(msg, t) { 64 | try { 65 | var {values, newSAB} = this._marshaler.marshal([msg]); 66 | } 67 | catch (e) { 68 | // TODO: This could be improved by making the Marshaler throw useful errors. 69 | throw new ChannelEncodingError("Marshaler failed:\n" + e); 70 | } 71 | if (newSAB.length) 72 | throw new ChannelEncodingError("SharedArrayBuffer not supported"); 73 | return IntQueue.enqueue(this._queue, values, t); 74 | } 75 | 76 | /* 77 | * Create a receiver endpoint. See comments on the sender endpoint. 78 | */ 79 | function ChannelReceiver(shared_state) { 80 | this._queue = shared_state; 81 | this._marshaler = new Marshaler(); 82 | } 83 | 84 | /* 85 | * Receive a message from the channel, waiting for up to t 86 | * milliseconds (undefined == indefinite wait) until there is a 87 | * message if necessary. Returns the message, or the noMessage value 88 | * if none was received. 89 | */ 90 | ChannelReceiver.prototype.receive = function (t, noMessage) { 91 | var M = IntQueue.dequeue(this._queue, t); 92 | if (M == null) 93 | return noMessage; 94 | return this._marshaler.unmarshal(M, 0, M.length)[0]; 95 | } 96 | 97 | /* 98 | * Error object. 99 | */ 100 | function ChannelEncodingError(message) { 101 | this.message = message; 102 | } 103 | ChannelEncodingError.prototype = new Error; 104 | -------------------------------------------------------------------------------- /demo/channel/intqueue.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 | 6 | /* 7 | * Simple multi-producer and multi-consumer shared-memory queue for 8 | * transmitting arrays of Int32 values - a useful building block for 9 | * other mechanisms. 10 | * 11 | * This version is built on flatjs. The original version, in 12 | * parlib-simple, used hand-built shared-memory data structures. 13 | */ 14 | 15 | @flatjs class IntQueue { 16 | spaceAvailable: int32.synchronic 17 | dataAvailable: int32.synchronic 18 | 19 | lock: int32.synchronic 20 | head: int32 21 | tail: int32 22 | used: int32 23 | queue: int32.Array 24 | length: int32 25 | 26 | @method init(SELF, length) { 27 | SELF.length = length; 28 | SELF.queue = @new int32.Array(length); 29 | return SELF; 30 | } 31 | 32 | @method enqueue(SELF, ints, timeout) { 33 | var required = ints.length + 1; 34 | 35 | if (!SELF.acquireWithSpaceAvailable(required, timeout)) 36 | return false; 37 | 38 | var q = SELF.queue; 39 | var qlen = SELF.length; 40 | var tail = SELF.tail; 41 | int32.Array.setAt(q, tail, ints.length); 42 | tail = (tail + 1) % qlen; 43 | for ( var i=0 ; i < ints.length ; i++ ) { 44 | int32.Array.setAt(q, tail, ints[i]); 45 | tail = (tail + 1) % qlen; 46 | } 47 | SELF.tail = tail; 48 | SELF.used += required; 49 | 50 | SELF.releaseWithDataAvailable(); 51 | return true; 52 | } 53 | 54 | @method dequeue(SELF, timeout) { 55 | if (!SELF.acquireWithDataAvailable(timeout)) 56 | return null; 57 | 58 | var A = []; 59 | var q = SELF.queue; 60 | var qlen = SELF.length; 61 | var head = SELF.head; 62 | var count = int32.Array.at(q, head); 63 | head = (head + 1) % qlen; 64 | while (count-- > 0) { 65 | A.push(int32.Array.at(q, head)); 66 | head = (head + 1) % qlen; 67 | } 68 | SELF.head = head; 69 | SELF.used -= A.length + 1; 70 | 71 | SELF.releaseWithSpaceAvailable(); 72 | return A; 73 | } 74 | 75 | @method acquireWithSpaceAvailable(SELF, required, t) { 76 | var limit = typeof t != "undefined" ? Date.now() + t : Number.POSITIVE_INFINITY; 77 | for (;;) { 78 | SELF.acquire(); 79 | var length = SELF.length; 80 | if (length - SELF.used >= required) 81 | return true; 82 | var probe = SELF.spaceAvailable; 83 | SELF.release(); 84 | if (required > length) 85 | throw new Error("Queue will never accept " + required + " words"); 86 | var remaining = limit - Date.now(); 87 | if (remaining <= 0) 88 | return false; 89 | SELF.spaceAvailable.expectUpdate(probe, remaining); 90 | } 91 | } 92 | 93 | @method acquireWithDataAvailable(SELF, t) { 94 | var limit = typeof t != "undefined" ? Date.now() + t : Number.POSITIVE_INFINITY; 95 | for (;;) { 96 | SELF.acquire(); 97 | if (SELF.used > 0) 98 | return true; 99 | var probe = SELF.dataAvailable; 100 | SELF.release(); 101 | var remaining = limit - Date.now(); 102 | if (remaining <= 0) 103 | return false; 104 | SELF.dataAvailable.expectUpdate(probe, remaining); 105 | } 106 | } 107 | 108 | @method releaseWithSpaceAvailable(SELF) { 109 | SELF.spaceAvailable += 1; 110 | SELF.release(); 111 | } 112 | 113 | @method releaseWithDataAvailable(SELF) { 114 | SELF.dataAvailable += 1; 115 | SELF.release(); 116 | } 117 | 118 | @method acquire(SELF) { 119 | while (SELF.lock.compareExchange(0, 1) != 0) 120 | SELF.lock.expectUpdate(1, Number.POSITIVE_INFINITY); 121 | } 122 | 123 | @method release(SELF) { 124 | SELF.lock = 0; 125 | } 126 | 127 | } @end 128 | -------------------------------------------------------------------------------- /demo/channel/intqueue.js: -------------------------------------------------------------------------------- 1 | // Generated from intqueue.flat_js by fjsc 0.5; github.com/lars-t-hansen/flatjs 2 | /* -*- mode: javascript -*- */ 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 | 7 | /* 8 | * Simple multi-producer and multi-consumer shared-memory queue for 9 | * transmitting arrays of Int32 values - a useful building block for 10 | * other mechanisms. 11 | * 12 | * This version is built on flatjs. The original version, in 13 | * parlib-simple, used hand-built shared-memory data structures. 14 | */ 15 | 16 | function IntQueue(p) { this._pointer = (p|0); } 17 | Object.defineProperty(IntQueue.prototype, 'pointer', { get: function () { return this._pointer } }); 18 | IntQueue.NAME = "IntQueue"; 19 | IntQueue.SIZE = 68; 20 | IntQueue.ALIGN = 4; 21 | IntQueue.CLSID = 160160494; 22 | Object.defineProperty(IntQueue, 'BASE', {get: function () { return null; }}); 23 | IntQueue.init = function (SELF, length) { 24 | _mem_int32[(SELF + 64) >> 2] = length; 25 | _mem_int32[(SELF + 60) >> 2] = (FlatJS.allocOrThrow(4 * length, 4)); 26 | return SELF; 27 | } 28 | IntQueue.enqueue = function (SELF, ints, timeout) { 29 | var required = ints.length + 1; 30 | 31 | if (!IntQueue.acquireWithSpaceAvailable(SELF, required, timeout)) 32 | return false; 33 | 34 | var q = _mem_int32[(SELF + 60) >> 2]; 35 | var qlen = _mem_int32[(SELF + 64) >> 2]; 36 | var tail = _mem_int32[(SELF + 52) >> 2]; 37 | _mem_int32[(q+4*tail) >> 2] = (ints.length); 38 | tail = (tail + 1) % qlen; 39 | for ( var i=0 ; i < ints.length ; i++ ) { 40 | _mem_int32[(q+4*tail) >> 2] = (ints[i]); 41 | tail = (tail + 1) % qlen; 42 | } 43 | _mem_int32[(SELF + 52) >> 2] = tail; 44 | _mem_int32[(SELF + 56) >> 2] += required; 45 | 46 | IntQueue.releaseWithDataAvailable(SELF ); 47 | return true; 48 | } 49 | IntQueue.dequeue = function (SELF, timeout) { 50 | if (!IntQueue.acquireWithDataAvailable(SELF, timeout)) 51 | return null; 52 | 53 | var A = []; 54 | var q = _mem_int32[(SELF + 60) >> 2]; 55 | var qlen = _mem_int32[(SELF + 64) >> 2]; 56 | var head = _mem_int32[(SELF + 48) >> 2]; 57 | var count = _mem_int32[(q+4*head) >> 2]; 58 | head = (head + 1) % qlen; 59 | while (count-- > 0) { 60 | A.push(_mem_int32[(q+4*head) >> 2]); 61 | head = (head + 1) % qlen; 62 | } 63 | _mem_int32[(SELF + 48) >> 2] = head; 64 | _mem_int32[(SELF + 56) >> 2] -= (A.length + 1); 65 | 66 | IntQueue.releaseWithSpaceAvailable(SELF ); 67 | return A; 68 | } 69 | IntQueue.acquireWithSpaceAvailable = function (SELF, required, t) { 70 | var limit = typeof t != "undefined" ? Date.now() + t : Number.POSITIVE_INFINITY; 71 | for (;;) { 72 | IntQueue.acquire(SELF ); 73 | var length = _mem_int32[(SELF + 64) >> 2]; 74 | if (length - _mem_int32[(SELF + 56) >> 2] >= required) 75 | return true; 76 | var probe = Atomics.load(_mem_int32, ((SELF + 4) + 8) >> 2); 77 | IntQueue.release(SELF ); 78 | if (required > length) 79 | throw new Error("Queue will never accept " + required + " words"); 80 | var remaining = limit - Date.now(); 81 | if (remaining <= 0) 82 | return false; 83 | FlatJS._synchronicExpectUpdate((SELF + 4), _mem_int32, ((SELF + 4) + 8) >> 2, probe, remaining); 84 | } 85 | } 86 | IntQueue.acquireWithDataAvailable = function (SELF, t) { 87 | var limit = typeof t != "undefined" ? Date.now() + t : Number.POSITIVE_INFINITY; 88 | for (;;) { 89 | IntQueue.acquire(SELF ); 90 | if (_mem_int32[(SELF + 56) >> 2] > 0) 91 | return true; 92 | var probe = Atomics.load(_mem_int32, ((SELF + 16) + 8) >> 2); 93 | IntQueue.release(SELF ); 94 | var remaining = limit - Date.now(); 95 | if (remaining <= 0) 96 | return false; 97 | FlatJS._synchronicExpectUpdate((SELF + 16), _mem_int32, ((SELF + 16) + 8) >> 2, probe, remaining); 98 | } 99 | } 100 | IntQueue.releaseWithSpaceAvailable = function (SELF) { 101 | FlatJS._synchronicAdd((SELF + 4), _mem_int32, ((SELF + 4) + 8) >> 2, 1); 102 | IntQueue.release(SELF ); 103 | } 104 | IntQueue.releaseWithDataAvailable = function (SELF) { 105 | FlatJS._synchronicAdd((SELF + 16), _mem_int32, ((SELF + 16) + 8) >> 2, 1); 106 | IntQueue.release(SELF ); 107 | } 108 | IntQueue.acquire = function (SELF) { 109 | while (FlatJS._synchronicCompareExchange((SELF + 36), _mem_int32, ((SELF + 36) + 8) >> 2, 0, 1) != 0) 110 | FlatJS._synchronicExpectUpdate((SELF + 36), _mem_int32, ((SELF + 36) + 8) >> 2, 1, (Number.POSITIVE_INFINITY)); 111 | } 112 | IntQueue.release = function (SELF) { 113 | FlatJS._synchronicStore((SELF + 36), _mem_int32, ((SELF + 36) + 8) >> 2, 0); 114 | } 115 | IntQueue.initInstance = function(SELF) { _mem_int32[SELF>>2]=160160494; return SELF; } 116 | FlatJS._idToType[160160494] = IntQueue; 117 | -------------------------------------------------------------------------------- /demo/channel/test-sendmsg-master.js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 | 7 | var iterations = 100000; 8 | var qsize = 4096; 9 | 10 | var w = new Worker("test-sendmsg-worker.js"); 11 | 12 | var sab = new SharedArrayBuffer(65536); 13 | FlatJS.init(sab, 0, sab.byteLength, true); 14 | 15 | // Setup our state first. 16 | 17 | var channel_state = makeChannelSharedState(qsize); 18 | 19 | var s = new ChannelSender(channel_state); 20 | var r = new ChannelReceiver(channel_state); 21 | 22 | // Kick off the worker and wait for a message that it is ready. 23 | 24 | w.onmessage = workerReady; 25 | w.postMessage([sab, iterations, channel_state]); 26 | 27 | console.log("Master waiting"); 28 | 29 | function workerReady(ev) { 30 | var start = Date.now(); 31 | 32 | var c = {item:0}; 33 | for ( var i=0 ; i < iterations ; i++ ) { 34 | s.send(c); 35 | c = r.receive(); 36 | } 37 | 38 | var end = Date.now(); 39 | 40 | console.log("Should be " + iterations + ": " + c.item); 41 | console.log(Math.round(1000 * (2*iterations) / (end - start)) + " messages/s"); 42 | } 43 | -------------------------------------------------------------------------------- /demo/channel/test-sendmsg-worker.js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 | 7 | importScripts("../../../parlib-simple/src/marshaler.js", 8 | "../../libflatjs.js", 9 | "intqueue.js", 10 | "channel.js"); 11 | 12 | onmessage = 13 | function (ev) { 14 | var [sab, iterations, channel_state] = ev.data; 15 | FlatJS.init(sab, 0, sab.byteLength, false); 16 | 17 | var r = new ChannelReceiver(channel_state); 18 | var s = new ChannelSender(channel_state); 19 | 20 | // Let the master know we're ready to go 21 | 22 | postMessage("ready"); 23 | 24 | var c = {item:-1}; 25 | for ( var i=0 ; i < iterations ; i++ ) { 26 | c = r.receive(); 27 | c.item++; 28 | s.send(c); 29 | } 30 | 31 | console.log("Worker exiting"); 32 | }; 33 | -------------------------------------------------------------------------------- /demo/channel/test-sendmsg.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demo/ray-flatjs/README.md: -------------------------------------------------------------------------------- 1 | Ray tracer - FlatJS sequential code. 2 | 3 | There is also a data-parallel version using FlatJS with the Par 4 | framework, see demo/ray-flatjs in the parlib-simple repository. 5 | 6 | (expected-output.ppm is the expected output with all features turned on.) 7 | -------------------------------------------------------------------------------- /demo/ray-flatjs/expected-output.ppm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lars-t-hansen/flatjs/8e23cacffe606cb5e20fd34fbfc468cc5c07e071/demo/ray-flatjs/expected-output.ppm -------------------------------------------------------------------------------- /demo/ray-flatjs/ray.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | // Ray tracer, largely out of Shirley & Marschner 3rd Ed. 4 | // Traces a scene and writes to a canvas. 5 | // 6 | // lth@acm.org / lhansen@mozilla.com, winter 2012 and later. 7 | // 8 | // This is written in flatjs, holding the scene graph in flat memory 9 | // and rendering into an ArrayBuffer. The computation is straight JS. 10 | // 11 | // This is about 25% faster running on 1 core than the JavaScript 12 | // version that uses native objects (3900ms vs 5300ms on my late-2013 13 | // MacBook Pro, 2.6GHz i7). That's before trying to optimize much, 14 | // apart from getting rid of varargs in vcalls. 15 | 16 | // Global parameters 17 | 18 | const height = 600; 19 | const width = 800; 20 | 21 | // FlatJS setup. 22 | 23 | const RAW_MEMORY = new ArrayBuffer(height*width*4 + 65536); 24 | FlatJS.init(RAW_MEMORY, 0, RAW_MEMORY.byteLength, true); 25 | 26 | // CONFIGURATION 27 | 28 | const shadows = true; // Compute object shadows 29 | const reflection = true; // Compute object reflections 30 | const reflection_depth = 2; 31 | const antialias = false; // true; // Antialias the image (expensive but pretty) 32 | 33 | // END CONFIGURATION 34 | 35 | const debug = false; // Progress printout, may confuse the consumer 36 | 37 | const SENTINEL = 1e32; 38 | const EPS = 0.00001; 39 | 40 | function DL3(x, y, z) { return {x:x, y:y, z:z}; } 41 | 42 | function add(a, b) { return DL3(a.x+b.x, a.y+b.y, a.z+b.z); } 43 | function addi(a, c) { return DL3(a.x+c, a.y+c, a.z+c); } 44 | function sub(a, b) { return DL3(a.x-b.x, a.y-b.y, a.z-b.z); } 45 | function subi(a, c) { return DL3(a.x-c, a.y-c, a.z-c); } 46 | function muli(a, c) { return DL3(a.x*c, a.y*c, a.z*c); } 47 | function divi(a, c) { return DL3(a.x/c, a.y/c, a.z/c); } 48 | function neg(a) { return DL3(-a.x, -a.y, -a.z); } 49 | function length(a) { return Math.sqrt(a.x*a.x + a.y*a.y + a.z*a.z); } 50 | function normalize(a) { var d = length(a); return DL3(a.x/d, a.y/d, a.z/d); } 51 | function cross(a, b) { return DL3(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x); } 52 | function dot(a, b) { return a.x*b.x + a.y*b.y + a.z*b.z; } 53 | 54 | @flatjs struct Vec3 { 55 | x: float64 56 | y: float64 57 | z: float64 58 | 59 | @get(SELF) { 60 | return DL3(SELF.x, SELF.y, SELF.z); 61 | } 62 | } @end 63 | 64 | // Avoid intermediate DL3 objects 65 | 66 | function subvref(a, b) { return DL3(a.x-Vec3.x(b), a.y-Vec3.y(b), a.z-Vec3.z(b)); } 67 | function subrefref(a, b) { return DL3(Vec3.x(a)-Vec3.x(b), Vec3.y(a)-Vec3.y(b), Vec3.z(a)-Vec3.z(b)); } 68 | function mulrefi(a, c) { return DL3(Vec3.x(a)*c, Vec3.y(a)*c, Vec3.z(a)*c); } 69 | 70 | @flatjs struct Material { 71 | diffuse: Vec3 72 | specular: Vec3 73 | shininess: float64 74 | ambient: Vec3 75 | mirror: float64 76 | } @end 77 | 78 | function makeMaterial(diffuse, specular, shininess, ambient, mirror) { 79 | var v = new Material; 80 | v.diffuse = diffuse; 81 | v.specular = specular; 82 | v.shininess = shininess; 83 | v.ambient = ambient; 84 | v.mirror = mirror; 85 | return v; 86 | } 87 | 88 | @flatjs class Surface { 89 | material: Material 90 | 91 | @method init(SELF, material) { 92 | SELF.material = material; 93 | return SELF; 94 | } 95 | 96 | @virtual intersect(SELF, eye, ray, min, max) { 97 | throw "Pure: Surface.intersect" 98 | } 99 | 100 | @virtual normal(SELF, p) { 101 | throw "Pure: Surface.normal" 102 | } 103 | } @end 104 | 105 | @flatjs class Scene extends Surface { 106 | length: int32 107 | objects: Surface.Array 108 | 109 | @method init(SELF, objects) { 110 | var len = objects.length; 111 | SELF.length = len; 112 | var objs = @new Surface.Array(len); 113 | for ( var i=0 ; i < len ; i++ ) 114 | Surface.Array.setAt(objs, i, objects[i]); 115 | SELF.objects = objs; 116 | return SELF; 117 | } 118 | 119 | @virtual intersect(SELF, eye, ray, min, max) { 120 | var min_obj = NULL; 121 | var min_dist = SENTINEL; 122 | 123 | var objs = SELF.objects; 124 | for ( var idx=0, limit=SELF.length ; idx < limit ; idx++ ) { 125 | var surf = Surface.Array.at(objs, idx); 126 | var tmp = Surface.intersect(surf, eye, ray, min, max); 127 | var obj = tmp.obj; 128 | var dist = tmp.dist; 129 | if (obj) 130 | if (dist >= min && dist < max) 131 | if (dist < min_dist) { 132 | min_obj = obj; 133 | min_dist = dist; 134 | } 135 | } 136 | return {obj:min_obj, dist:min_dist}; 137 | } 138 | 139 | } @end 140 | 141 | @flatjs class Sphere extends Surface { 142 | center: Vec3 143 | radius: float64 144 | 145 | @method init(SELF, material, center, radius) { 146 | Surface.init(SELF, material) 147 | SELF.center = center; 148 | SELF.radius = radius 149 | return SELF; 150 | } 151 | 152 | @virtual intersect(SELF, eye, ray, min, max) { 153 | var DdotD = dot(ray, ray); 154 | var EminusC = subvref(eye, SELF.center.ref); 155 | var B = dot(ray, EminusC); 156 | var disc = B*B - DdotD*(dot(EminusC,EminusC) - SELF.radius*SELF.radius); 157 | if (disc < 0.0) 158 | return {obj:NULL, dist:0}; 159 | var s1 = (-B + Math.sqrt(disc))/DdotD; 160 | var s2 = (-B - Math.sqrt(disc))/DdotD; 161 | // Here return the smallest of s1 and s2 after filtering for _min and _max 162 | if (s1 < min || s1 > max) 163 | s1 = SENTINEL; 164 | if (s2 < min || s2 > max) 165 | s2 = SENTINEL; 166 | var _dist = Math.min(s1,s2); 167 | if (_dist == SENTINEL) 168 | return {obj:NULL, dist:0}; 169 | return {obj:SELF, dist:_dist}; 170 | } 171 | 172 | @virtual normal(SELF, p) { 173 | return divi(subvref(p, SELF.center.ref), SELF.radius); 174 | } 175 | } @end 176 | 177 | @flatjs class Triangle extends Surface { 178 | v1: Vec3 179 | v2: Vec3 180 | v3: Vec3 181 | 182 | @method init(SELF, material, v1, v2, v3) { 183 | Surface.init(SELF, material) 184 | SELF.v1 = v1; 185 | SELF.v2 = v2; 186 | SELF.v3 = v3; 187 | return SELF; 188 | } 189 | 190 | @virtual intersect(SELF, eye, ray, min, max) { 191 | // TODO: observe that values that do not depend on g, h, and i can be precomputed 192 | // and stored with the triangle (for a given eye position), at some (possibly significant) 193 | // space cost. Notably the numerator of "t" is invariant, as are many factors of the 194 | // numerator of "gamma". 195 | var a = SELF.v1.x - SELF.v2.x; 196 | var b = SELF.v1.y - SELF.v2.y; 197 | var c = SELF.v1.z - SELF.v2.z; 198 | var d = SELF.v1.x - SELF.v3.x; 199 | var e = SELF.v1.y - SELF.v3.y; 200 | var f = SELF.v1.z - SELF.v3.z; 201 | var g = ray.x; 202 | var h = ray.y; 203 | var i = ray.z; 204 | var j = SELF.v1.x - eye.x; 205 | var k = SELF.v1.y - eye.y; 206 | var l = SELF.v1.z - eye.z; 207 | var M = a*(e*i - h*f) + b*(g*f - d*i) + c*(d*h - e*g); 208 | var t = -((f*(a*k - j*b) + e*(j*c - a*l) + d*(b*l - k*c))/M); 209 | if (t < min || t > max) 210 | return {obj:NULL,dist:0}; 211 | var gamma = (i*(a*k - j*b) + h*(j*c - a*l) + g*(b*l - k*c))/M; 212 | if (gamma < 0 || gamma > 1.0) 213 | return {obj:NULL,dist:0}; 214 | var beta = (j*(e*i - h*f) + k*(g*f - d*i) + l*(d*h - e*g))/M; 215 | if (beta < 0.0 || beta > 1.0 - gamma) 216 | return {obj:NULL,dist:0}; 217 | return {obj:SELF, dist:t}; 218 | } 219 | 220 | @virtual normal(SELF, p) { 221 | // TODO: Observe that the normal is invariant and can be stored with the triangle 222 | return normalize(cross(subrefref(SELF.v2.ref, SELF.v1.ref), subrefref(SELF.v3.ref, SELF.v1.ref))); 223 | } 224 | 225 | } @end 226 | 227 | @flatjs class Bitmap { 228 | data: int32.Array 229 | height: int32 230 | width: int32 231 | 232 | @method init(SELF, height, width, color) { 233 | SELF.height = height; 234 | SELF.width = width; 235 | var data = @new int32.Array(height*width); 236 | var c = (255<<24)|((255*color.z)<<16)|((255*color.y)<<8)|(255*color.x) 237 | for ( var i=0, l=width*height ; i < l ; i++ ) 238 | int32.Array.setAt(data, i, c); 239 | SELF.data = data; 240 | return SELF; 241 | } 242 | 243 | // For debugging only 244 | @method ref(SELF, y, x) { 245 | return int32.Array.at(SELF.data, (SELF.height-y)*SELF.width+x); 246 | } 247 | 248 | // Not a hot function 249 | @method setColor(SELF, y, x, v) { 250 | int32.Array.setAt(SELF.data, (SELF.height-y-1)*SELF.width+x, (255<<24)|((255*v.z)<<16)|((255*v.y)<<8)|(255*v.x)); 251 | } 252 | } @end 253 | 254 | const g_left = -2; 255 | const g_right = 2; 256 | const g_top = 1.5; 257 | const g_bottom = -1.5; 258 | 259 | const bits = Bitmap.init(@new Bitmap, height, width, DL3(152.0/256.0, 251.0/256.0, 152.0/256.0)); 260 | 261 | function main() { 262 | setStage(); 263 | 264 | // No workers, we do this all on the main thread, but the data is 265 | // in shared memory. Normally we would otherwise communicate 266 | // [sab, width, height, bits] and maybe something coordinative. 267 | 268 | var then = Date.now(); 269 | trace(0, height); 270 | var now = Date.now(); 271 | 272 | var mycanvas = document.getElementById("mycanvas"); 273 | var cx = mycanvas.getContext('2d'); 274 | var id = cx.createImageData(width, height); 275 | // FIXME: will set() work properly? 276 | // TODO: This operation, extracting a typed array from raw memory at an address, 277 | // could usefully be added to libflatjs, to avoid dealing with RAW_MEMORY. 278 | id.data.set(new Uint8Array(RAW_MEMORY, Bitmap.data(bits), width*height*4)); 279 | cx.putImageData( id, 0, 0 ); 280 | document.getElementById("mycaption").innerHTML = "Time=" + (now - then) + "ms"; 281 | 282 | return 0; 283 | } 284 | 285 | const zzz = DL3(0,0,0); 286 | 287 | var eye = zzz; // Eye coordinates 288 | var light = zzz; // Light source coordinates 289 | var background = zzz; // Background color 290 | var world = NULL; 291 | 292 | // Colors: http://kb.iu.edu/data/aetf.html 293 | 294 | const paleGreen = DL3(152.0/256.0, 251.0/256.0, 152.0/256.0); 295 | const darkGray = DL3(169.0/256.0, 169.0/256.0, 169.0/256.0); 296 | const yellow = DL3(1.0, 1.0, 0.0); 297 | const red = DL3(1.0, 0.0, 0.0); 298 | const blue = DL3(0.0, 0.0, 1.0); 299 | 300 | // Not restricted to a rectangle, actually 301 | function rectangle(world, m, v1, v2, v3, v4) { 302 | world.push(Triangle.init(@new Triangle, m, v1, v2, v3)); 303 | world.push(Triangle.init(@new Triangle, m, v1, v3, v4)); 304 | } 305 | 306 | // Vertices are for front and back faces, both counterclockwise as seen 307 | // from the outside. 308 | // Not restricted to a cube, actually. 309 | function cube(world, m, v1, v2, v3, v4, v5, v6, v7, v8) { 310 | rectangle(world, m, v1, v2, v3, v4); // front 311 | rectangle(world, m, v2, v5, v8, v3); // right 312 | rectangle(world, m, v6, v1, v4, v7); // left 313 | rectangle(world, m, v5, v5, v7, v8); // back 314 | rectangle(world, m, v4, v3, v8, v7); // top 315 | rectangle(world, m, v6, v5, v2, v1); // bottom 316 | } 317 | 318 | function setStage() { 319 | if (debug) 320 | PRINT("Setstage start"); 321 | 322 | const m1 = makeMaterial(DL3(0.1, 0.2, 0.2), DL3(0.3, 0.6, 0.6), 10, DL3(0.05, 0.1, 0.1), 0); 323 | const m2 = makeMaterial(DL3(0.3, 0.3, 0.2), DL3(0.6, 0.6, 0.4), 10, DL3(0.1,0.1,0.05), 0); 324 | const m3 = makeMaterial(DL3(0.1, 0, 0), DL3(0.8,0,0), 10, DL3(0.1,0,0), 0); 325 | const m4 = makeMaterial(muli(darkGray,0.4), muli(darkGray,0.3), 100, muli(darkGray,0.3), 0.5); 326 | const m5 = makeMaterial(muli(paleGreen,0.4), muli(paleGreen,0.4), 10, muli(paleGreen,0.2), 1.0); 327 | const m6 = makeMaterial(muli(yellow,0.6), zzz, 0, muli(yellow,0.4), 0); 328 | const m7 = makeMaterial(muli(red,0.6), zzz, 0, muli(red,0.4), 0); 329 | const m8 = makeMaterial(muli(blue,0.6), zzz, 0, muli(blue,0.4), 0); 330 | 331 | var world = []; 332 | 333 | world.push(Sphere.init(@new Sphere, m1, DL3(-1, 1, -9), 1)); 334 | world.push(Sphere.init(@new Sphere, m2, DL3(1.5, 1, 0), 0.75)); 335 | world.push(Triangle.init(@new Triangle, m1, DL3(-1,0,0.75), DL3(-0.75,0,0), DL3(-0.75,1.5,0))); 336 | world.push(Triangle.init(@new Triangle, m3, DL3(-2,0,0), DL3(-0.5,0,0), DL3(-0.5,2,0))); 337 | rectangle(world, m4, DL3(-5,0,5), DL3(5,0,5), DL3(5,0,-40), DL3(-5,0,-40)); 338 | cube(world, m5, DL3(1, 1.5, 1.5), DL3(1.5, 1.5, 1.25), DL3(1.5, 1.75, 1.25), DL3(1, 1.75, 1.5), 339 | DL3(1.5, 1.5, 0.5), DL3(1, 1.5, 0.75), DL3(1, 1.75, 0.75), DL3(1.5, 1.75, 0.5)); 340 | for ( var i=0 ; i < 30 ; i++ ) 341 | world.push(Sphere.init(@new Sphere, m6, DL3((-0.6+(i*0.2)), (0.075+(i*0.05)), (1.5-(i*Math.cos(i/30.0)*0.5))), 0.075)); 342 | for ( var i=0 ; i < 60 ; i++ ) 343 | world.push(Sphere.init(@new Sphere, m7, DL3((1+0.3*Math.sin(i*(3.14/16))), (0.075+(i*0.025)), (1+0.3*Math.cos(i*(3.14/16)))), 0.025)); 344 | for ( var i=0 ; i < 60 ; i++ ) 345 | world.push(Sphere.init(@new Sphere, m8, DL3((1+0.3*Math.sin(i*(3.14/16))), (0.075+((i+8)*0.025)), (1+0.3*Math.cos(i*(3.14/16)))), 0.025)); 346 | 347 | this.world = Scene.init(@new Scene, world); 348 | 349 | eye = DL3(0.5, 0.75, 5); 350 | light = DL3(g_left-1, g_top, 2); 351 | background = DL3(25.0/256.0,25.0/256.0,112.0/256.0); 352 | if (debug) 353 | PRINT("Setstage end"); 354 | } 355 | 356 | function trace(hmin, hlim) { 357 | if (antialias) 358 | traceWithAntialias(hmin, hlim); 359 | else 360 | traceWithoutAntialias(hmin, hlim); 361 | } 362 | 363 | function traceWithoutAntialias(hmin, hlim) { 364 | for ( var h=hmin ; h < hlim ; h++ ) { 365 | if (debug) 366 | PRINT("Row " + h); 367 | for ( var w=0 ; w < width ; w++ ) { 368 | var u = g_left + (g_right - g_left)*(w + 0.5)/width; 369 | var v = g_bottom + (g_top - g_bottom)*(h + 0.5)/height; 370 | var ray = DL3(u, v, -eye.z); 371 | var col = raycolor(eye, ray, 0, SENTINEL, reflection_depth); 372 | Bitmap.setColor(bits, h, w, col); 373 | } 374 | } 375 | } 376 | 377 | const random_numbers = [ 378 | 0.495,0.840,0.636,0.407,0.026,0.547,0.223,0.349,0.033,0.643,0.558,0.481,0.039, 379 | 0.175,0.169,0.606,0.638,0.364,0.709,0.814,0.206,0.346,0.812,0.603,0.969,0.888, 380 | 0.294,0.824,0.410,0.467,0.029,0.706,0.314 381 | ]; 382 | 383 | function traceWithAntialias(hmin, hlim) { 384 | var k = 0; 385 | for ( var h=hmin ; h < hlim ; h++ ) { 386 | //if (debug) 387 | // PRINT("Row " + h); 388 | for ( var w=0 ; w < width ; w++ ) { 389 | // Simple stratified sampling, cf Shirley&Marschner ch 13 and a fast "random" function. 390 | const n = 4; 391 | //var k = h % 32; 392 | var rand = k % 2; 393 | var c = zzz; 394 | k++; 395 | for ( var p=0 ; p < n ; p++ ) { 396 | for ( var q=0 ; q < n ; q++ ) { 397 | var jx = random_numbers[rand]; rand=rand+1; 398 | var jy = random_numbers[rand]; rand=rand+1; 399 | var u = g_left + (g_right - g_left)*(w + (p + jx)/n)/width; 400 | var v = g_bottom + (g_top - g_bottom)*(h + (q + jy)/n)/height; 401 | var ray = DL3(u, v, -eye.z); 402 | c = add(c, raycolor(eye, ray, 0.0, SENTINEL, reflection_depth)); 403 | } 404 | } 405 | Bitmap.setColor(bits, h,w,divi(c,n*n)); 406 | } 407 | } 408 | } 409 | 410 | // Clamping c is not necessary provided the three color components by 411 | // themselves never add up to more than 1, and shininess == 0 or shininess >= 1. 412 | // 413 | // TODO: lighting intensity is baked into the material here, but we probably want 414 | // to factor that out and somehow attenuate light with distance from the light source, 415 | // for diffuse and specular lighting. 416 | 417 | function raycolor(eye, ray, t0, t1, depth) { 418 | var tmp = Surface.intersect(world, eye, ray, t0, t1); 419 | var obj = tmp.obj; 420 | var dist = tmp.dist; 421 | 422 | if (obj) { 423 | const m = Surface.material.ref(obj); 424 | const p = add(eye, muli(ray, dist)); 425 | const n1 = Surface.normal(obj, p); 426 | const l1 = normalize(sub(light, p)); 427 | var c = Material.ambient(m); 428 | var min_obj = NULL; 429 | 430 | // Passing NULL here and testing for it in intersect() was intended as an optimization, 431 | // since any hit will do, but does not seem to have much of an effect in scenes tested 432 | // so far - maybe not enough scene detail (too few shadows). 433 | if (shadows) { 434 | var tmp = Surface.intersect(world, add(p, muli(l1, EPS)), l1, EPS, SENTINEL); 435 | min_obj = tmp.obj; 436 | } 437 | if (!min_obj) { 438 | const diffuse = Math.max(0.0, dot(n1,l1)); 439 | const v1 = normalize(neg(ray)); 440 | const h1 = normalize(add(v1, l1)); 441 | const specular = Math.pow(Math.max(0.0, dot(n1, h1)), Material.shininess(m)); 442 | c = add(c, add(mulrefi(Material.diffuse.ref(m),diffuse), mulrefi(Material.specular.ref(m),specular))); 443 | if (reflection) 444 | if (depth > 0.0 && Material.mirror(m) != 0.0) { 445 | const r = sub(ray, muli(n1, 2.0*dot(ray, n1))); 446 | c = add(c, muli(raycolor(add(p, muli(r,EPS)), r, EPS, SENTINEL, depth-1), Material.mirror(m))); 447 | } 448 | } 449 | return c; 450 | } 451 | return background; 452 | } 453 | 454 | function fail(msg) { 455 | PRINT(""); 456 | PRINT(msg); 457 | throw new Error("EXIT"); 458 | } 459 | -------------------------------------------------------------------------------- /demo/ray-flatjs/ray.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |
7 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /demo/ray-simd/README.md: -------------------------------------------------------------------------------- 1 | Ray tracer - sequential code. 2 | 3 | expected-output.ppm is the expected output with all features turned on. 4 | -------------------------------------------------------------------------------- /demo/ray-simd/expected-output.ppm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lars-t-hansen/flatjs/8e23cacffe606cb5e20fd34fbfc468cc5c07e071/demo/ray-simd/expected-output.ppm -------------------------------------------------------------------------------- /demo/ray-simd/ray.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |
7 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/ray-simd/ray.js: -------------------------------------------------------------------------------- 1 | // Ray tracer, largely out of Shirley & Marschner 3rd Ed. 2 | // Traces a scene and writes to a canvas. 3 | // 4 | // lth@acm.org / lhansen@mozilla.com, winter 2012 and later. 5 | // 6 | // The language is Javascript+SIMD. This runs in Firefox Nightly, at 7 | // least, but only at about the same speed as the non-SIMD version. 8 | // The dot product can probably be improved, but even if its share 9 | // were to fall from 25% to 0% we'd just be neck-and-neck with the 10 | // plain-JS flatjs version. 11 | 12 | // CONFIGURATION 13 | 14 | const shadows = true; // Compute object shadows 15 | const reflection = true; // Compute object reflections 16 | const reflection_depth = 2; 17 | const antialias = false; // true; // Antialias the image (expensive but pretty) 18 | 19 | // END CONFIGURATION 20 | 21 | const debug = false; // Progress printout, may confuse the consumer 22 | 23 | const SENTINEL = 1e32; 24 | const EPS = 0.00001; 25 | 26 | function DL3(x, y, z) { return SIMD.float32x4(x,y,z,0); } 27 | 28 | function add(a, b) { return SIMD.float32x4.add(a, b); } 29 | function addi(a, c) { return SIMD.float32x4.add(a, SIMD.float32x4(c,c,c,0)) } 30 | function sub(a, b) { return SIMD.float32x4.sub(a, b); } 31 | function subi(a, c) { return SIMD.float32x4.sub(a, SIMD.float32x4(c,c,c,0)) } 32 | function muli(a, c) { return SIMD.float32x4.mul(a, SIMD.float32x4(c,c,c,0)) } 33 | function divi(a, c) { return SIMD.float32x4.div(a, SIMD.float32x4(c,c,c,0)) } // Note, do we get NaN in the last slot now? 34 | function neg(a) { return SIMD.float32x4.neg(a); } // Note, do we get -0 in the last slot now? 35 | function length(a) { var v = SIMD.float32x4.mul(a,a); return Math.sqrt(v.x + v.y + v.z); } 36 | function normalize(a) { var d = length(a); return SIMD.float32x4.div(a, SIMD.float32x4(d,d,d,0)) } 37 | function cross(a, b) { return SIMD.float32x4(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x, 0); } 38 | function dot(a, b) { return a.x*b.x + a.y*b.y + a.z*b.z; } 39 | 40 | function Material(diffuse, specular, shininess, ambient, mirror) { 41 | this.diffuse = diffuse; 42 | this.specular = specular; 43 | this.shininess = shininess; 44 | this.ambient = ambient; 45 | this.mirror = mirror; 46 | } 47 | 48 | const zzz = DL3(0,0,0); 49 | const m0 = new Material(zzz, zzz, 0, zzz, 0); 50 | 51 | function Scene() { 52 | this.material = m0; 53 | this.objects = []; 54 | } 55 | 56 | Scene.prototype.add = 57 | function(obj) { 58 | this.objects.push(obj); 59 | }; 60 | 61 | Scene.prototype.intersect = 62 | function (eye, ray, min, max) { 63 | var min_obj = null; 64 | var min_dist = SENTINEL; 65 | 66 | var objs = this.objects; 67 | for ( var idx=0, limit=objs.length ; idx < limit ; idx++ ) { 68 | var surf = objs[idx]; 69 | var tmp = surf.intersect(eye, ray, min, max); 70 | var obj = tmp.obj; 71 | var dist = tmp.dist; 72 | if (obj) 73 | if (dist >= min && dist < max) 74 | if (dist < min_dist) { 75 | min_obj = obj; 76 | min_dist = dist; 77 | } 78 | } 79 | return {obj:min_obj, dist:min_dist}; 80 | }; 81 | 82 | Scene.prototype.normal = 83 | function (p) { 84 | fail("normal() not defined on Scene"); 85 | return zzz; 86 | }; 87 | 88 | function Sphere(material, center, radius) { 89 | this.material = material; 90 | this.center = center; 91 | this.radius = radius; 92 | } 93 | 94 | Sphere.prototype.intersect = 95 | function (eye, ray, min, max) { 96 | var DdotD = dot(ray, ray); 97 | var EminusC = sub(eye, this.center); 98 | var B = dot(ray, EminusC); 99 | var disc = B*B - DdotD*(dot(EminusC,EminusC) - this.radius*this.radius); 100 | if (disc < 0.0) 101 | return {obj:null, dist:0}; 102 | var s1 = (-B + Math.sqrt(disc))/DdotD; 103 | var s2 = (-B - Math.sqrt(disc))/DdotD; 104 | // Here return the smallest of s1 and s2 after filtering for _min and _max 105 | if (s1 < min || s1 > max) 106 | s1 = SENTINEL; 107 | if (s2 < min || s2 > max) 108 | s2 = SENTINEL; 109 | var _dist = Math.min(s1,s2); 110 | if (_dist == SENTINEL) 111 | return {obj:null, dist:0}; 112 | return {obj:this, dist:_dist}; 113 | }; 114 | 115 | Sphere.prototype.normal = 116 | function (p) { 117 | return divi(sub(p, this.center), this.radius); 118 | }; 119 | 120 | function Triangle(material, v1, v2, v3) { 121 | this.material = material; 122 | this.v1 = v1; 123 | this.v2 = v2; 124 | this.v3 = v3; 125 | } 126 | 127 | Triangle.prototype.intersect = 128 | function (eye, ray, min, max) { 129 | // TODO: observe that values that do not depend on g, h, and i can be precomputed 130 | // and stored with the triangle (for a given eye position), at some (possibly significant) 131 | // space cost. Notably the numerator of "t" is invariant, as are many factors of the 132 | // numerator of "gamma". 133 | var v1 = this.v1; 134 | var v2 = this.v2; 135 | var v3 = this.v3; 136 | var a = v1.x - v2.x; 137 | var b = v1.y - v2.y; 138 | var c = v1.z - v2.z; 139 | var d = v1.x - v3.x; 140 | var e = v1.y - v3.y; 141 | var f = v1.z - v3.z; 142 | var g = ray.x; 143 | var h = ray.y; 144 | var i = ray.z; 145 | var j = v1.x - eye.x; 146 | var k = v1.y - eye.y; 147 | var l = v1.z - eye.z; 148 | var M = a*(e*i - h*f) + b*(g*f - d*i) + c*(d*h - e*g); 149 | var t = -((f*(a*k - j*b) + e*(j*c - a*l) + d*(b*l - k*c))/M); 150 | if (t < min || t > max) 151 | return {obj:null,dist:0}; 152 | var gamma = (i*(a*k - j*b) + h*(j*c - a*l) + g*(b*l - k*c))/M; 153 | if (gamma < 0 || gamma > 1.0) 154 | return {obj:null,dist:0}; 155 | var beta = (j*(e*i - h*f) + k*(g*f - d*i) + l*(d*h - e*g))/M; 156 | if (beta < 0.0 || beta > 1.0 - gamma) 157 | return {obj:null,dist:0}; 158 | return {obj:this, dist:t}; 159 | }; 160 | 161 | Triangle.prototype.normal = 162 | function (p) { 163 | // TODO: Observe that the normal is invariant and can be stored with the triangle 164 | return normalize(cross(sub(this.v2, this.v1), sub(this.v3, this.v1))); 165 | }; 166 | 167 | function Bitmap(height, width, color) { 168 | this.height = height; 169 | this.width = width; 170 | var c = (255<<24)|((255*color.z)<<16)|((255*color.y)<<8)|(255*color.x) 171 | var b = new Int32Array(width*height); 172 | this.data = b; 173 | for ( var i=0, l=width*height ; i < l ; i++ ) 174 | b[i] = c; 175 | } 176 | 177 | // For debugging only 178 | Bitmap.prototype.ref = 179 | function (y, x) { 180 | return this.data[(this.height-y)*this.width+x]; 181 | }; 182 | 183 | // Not a hot function 184 | Bitmap.prototype.setColor = 185 | function (y, x, v) { 186 | this.data[(this.height-y-1)*this.width+x] = (255<<24)|((255*v.z)<<16)|((255*v.y)<<8)|(255*v.x); 187 | }; 188 | 189 | const height = 600; 190 | const width = 800; 191 | 192 | const g_left = -2; 193 | const g_right = 2; 194 | const g_top = 1.5; 195 | const g_bottom = -1.5; 196 | 197 | const bits = new Bitmap(height, width, DL3(152.0/256.0, 251.0/256.0, 152.0/256.0)); 198 | 199 | function main() { 200 | setStage(); 201 | var then = Date.now(); 202 | trace(0, height); 203 | var now = Date.now(); 204 | 205 | var mycanvas = document.getElementById("mycanvas"); 206 | var cx = mycanvas.getContext('2d'); 207 | var id = cx.createImageData(width, height); 208 | id.data.set(new Uint8Array(bits.data.buffer)); 209 | cx.putImageData( id, 0, 0 ); 210 | document.getElementById("mycaption").innerHTML = "Time=" + (now - then) + "ms"; 211 | 212 | return 0; 213 | } 214 | 215 | var eye = zzz; // Eye coordinates 216 | var light = zzz; // Light source coordinates 217 | var background = zzz; // Background color 218 | var world = new Scene(); 219 | 220 | // Colors: http://kb.iu.edu/data/aetf.html 221 | 222 | const paleGreen = DL3(152.0/256.0, 251.0/256.0, 152.0/256.0); 223 | const darkGray = DL3(169.0/256.0, 169.0/256.0, 169.0/256.0); 224 | const yellow = DL3(1.0, 1.0, 0.0); 225 | const red = DL3(1.0, 0.0, 0.0); 226 | const blue = DL3(0.0, 0.0, 1.0); 227 | 228 | // Not restricted to a rectangle, actually 229 | function rectangle(m, v1, v2, v3, v4) { 230 | world.add(new Triangle(m, v1, v2, v3)); 231 | world.add(new Triangle(m, v1, v3, v4)); 232 | } 233 | 234 | // Vertices are for front and back faces, both counterclockwise as seen 235 | // from the outside. 236 | // Not restricted to a cube, actually. 237 | function cube(m, v1, v2, v3, v4, v5, v6, v7, v8) { 238 | rectangle(m, v1, v2, v3, v4); // front 239 | rectangle(m, v2, v5, v8, v3); // right 240 | rectangle(m, v6, v1, v4, v7); // left 241 | rectangle(m, v5, v5, v7, v8); // back 242 | rectangle(m, v4, v3, v8, v7); // top 243 | rectangle(m, v6, v5, v2, v1); // bottom 244 | } 245 | 246 | function setStage() { 247 | if (debug) 248 | PRINT("Setstage start"); 249 | const m1 = new Material(DL3(0.1, 0.2, 0.2), DL3(0.3, 0.6, 0.6), 10, DL3(0.05, 0.1, 0.1), 0); 250 | const m2 = new Material(DL3(0.3, 0.3, 0.2), DL3(0.6, 0.6, 0.4), 10, DL3(0.1,0.1,0.05), 0); 251 | const m3 = new Material(DL3(0.1, 0, 0), DL3(0.8,0,0), 10, DL3(0.1,0,0), 0); 252 | const m4 = new Material(muli(darkGray,0.4), muli(darkGray,0.3), 100, muli(darkGray,0.3), 0.5); 253 | const m5 = new Material(muli(paleGreen,0.4), muli(paleGreen,0.4), 10, muli(paleGreen,0.2), 1.0); 254 | const m6 = new Material(muli(yellow,0.6), zzz, 0, muli(yellow,0.4), 0); 255 | const m7 = new Material(muli(red,0.6), zzz, 0, muli(red,0.4), 0); 256 | const m8 = new Material(muli(blue,0.6), zzz, 0, muli(blue,0.4), 0); 257 | 258 | world.add(new Sphere(m1, DL3(-1, 1, -9), 1)); 259 | world.add(new Sphere(m2, DL3(1.5, 1, 0), 0.75)); 260 | world.add(new Triangle(m1, DL3(-1,0,0.75), DL3(-0.75,0,0), DL3(-0.75,1.5,0))); 261 | world.add(new Triangle(m3, DL3(-2,0,0), DL3(-0.5,0,0), DL3(-0.5,2,0))); 262 | rectangle(m4, DL3(-5,0,5), DL3(5,0,5), DL3(5,0,-40), DL3(-5,0,-40)); 263 | cube(m5, DL3(1, 1.5, 1.5), DL3(1.5, 1.5, 1.25), DL3(1.5, 1.75, 1.25), DL3(1, 1.75, 1.5), 264 | DL3(1.5, 1.5, 0.5), DL3(1, 1.5, 0.75), DL3(1, 1.75, 0.75), DL3(1.5, 1.75, 0.5)); 265 | for ( var i=0 ; i < 30 ; i++ ) 266 | world.add(new Sphere(m6, DL3((-0.6+(i*0.2)), (0.075+(i*0.05)), (1.5-(i*Math.cos(i/30.0)*0.5))), 0.075)); 267 | for ( var i=0 ; i < 60 ; i++ ) 268 | world.add(new Sphere(m7, DL3((1+0.3*Math.sin(i*(3.14/16))), (0.075+(i*0.025)), (1+0.3*Math.cos(i*(3.14/16)))), 0.025)); 269 | for ( var i=0 ; i < 60 ; i++ ) 270 | world.add(new Sphere(m8, DL3((1+0.3*Math.sin(i*(3.14/16))), (0.075+((i+8)*0.025)), (1+0.3*Math.cos(i*(3.14/16)))), 0.025)); 271 | 272 | eye = DL3(0.5, 0.75, 5); 273 | light = DL3(g_left-1, g_top, 2); 274 | background = DL3(25.0/256.0,25.0/256.0,112.0/256.0); 275 | if (debug) 276 | PRINT("Setstage end"); 277 | } 278 | 279 | function trace(hmin, hlim) { 280 | if (antialias) 281 | traceWithAntialias(hmin, hlim); 282 | else 283 | traceWithoutAntialias(hmin, hlim); 284 | } 285 | 286 | function traceWithoutAntialias(hmin, hlim) { 287 | for ( var h=hmin ; h < hlim ; h++ ) { 288 | if (debug) 289 | PRINT("Row " + h); 290 | for ( var w=0 ; w < width ; w++ ) { 291 | var u = g_left + (g_right - g_left)*(w + 0.5)/width; 292 | var v = g_bottom + (g_top - g_bottom)*(h + 0.5)/height; 293 | var ray = DL3(u, v, -eye.z); 294 | var col = raycolor(eye, ray, 0, SENTINEL, reflection_depth); 295 | bits.setColor(h, w, col); 296 | } 297 | } 298 | } 299 | 300 | const random_numbers = [ 301 | 0.495,0.840,0.636,0.407,0.026,0.547,0.223,0.349,0.033,0.643,0.558,0.481,0.039, 302 | 0.175,0.169,0.606,0.638,0.364,0.709,0.814,0.206,0.346,0.812,0.603,0.969,0.888, 303 | 0.294,0.824,0.410,0.467,0.029,0.706,0.314 304 | ]; 305 | 306 | function traceWithAntialias(hmin, hlim) { 307 | var k = 0; 308 | for ( var h=hmin ; h < hlim ; h++ ) { 309 | //if (debug) 310 | // PRINT("Row " + h); 311 | for ( var w=0 ; w < width ; w++ ) { 312 | // Simple stratified sampling, cf Shirley&Marschner ch 13 and a fast "random" function. 313 | const n = 4; 314 | //var k = h % 32; 315 | var rand = k % 2; 316 | var c = zzz; 317 | k++; 318 | for ( var p=0 ; p < n ; p++ ) { 319 | for ( var q=0 ; q < n ; q++ ) { 320 | var jx = random_numbers[rand]; rand=rand+1; 321 | var jy = random_numbers[rand]; rand=rand+1; 322 | var u = g_left + (g_right - g_left)*(w + (p + jx)/n)/width; 323 | var v = g_bottom + (g_top - g_bottom)*(h + (q + jy)/n)/height; 324 | var ray = DL3(u, v, -eye.z); 325 | c = add(c, raycolor(eye, ray, 0.0, SENTINEL, reflection_depth)); 326 | } 327 | } 328 | bits.setColor(h,w,divi(c,n*n)); 329 | } 330 | } 331 | } 332 | 333 | // Clamping c is not necessary provided the three color components by 334 | // themselves never add up to more than 1, and shininess == 0 or shininess >= 1. 335 | // 336 | // TODO: lighting intensity is baked into the material here, but we probably want 337 | // to factor that out and somehow attenuate light with distance from the light source, 338 | // for diffuse and specular lighting. 339 | 340 | function raycolor(eye, ray, t0, t1, depth) { 341 | var tmp = world.intersect(eye, ray, t0, t1); 342 | var obj = tmp.obj; 343 | var dist = tmp.dist; 344 | 345 | if (obj) { 346 | const m = obj.material; 347 | const p = add(eye, muli(ray, dist)); 348 | const n1 = obj.normal(p); 349 | const l1 = normalize(sub(light, p)); 350 | var c = m.ambient; 351 | var min_obj = null; 352 | 353 | // Passing NULL here and testing for it in intersect() was intended as an optimization, 354 | // since any hit will do, but does not seem to have much of an effect in scenes tested 355 | // so far - maybe not enough scene detail (too few shadows). 356 | if (shadows) { 357 | var tmp = world.intersect(add(p, muli(l1, EPS)), l1, EPS, SENTINEL); 358 | min_obj = tmp.obj; 359 | } 360 | if (!min_obj) { 361 | const diffuse = Math.max(0.0, dot(n1,l1)); 362 | const v1 = normalize(neg(ray)); 363 | const h1 = normalize(add(v1, l1)); 364 | const specular = Math.pow(Math.max(0.0, dot(n1, h1)), m.shininess); 365 | c = add(c, add(muli(m.diffuse,diffuse), muli(m.specular,specular))); 366 | if (reflection) 367 | if (depth > 0.0 && m.mirror != 0.0) { 368 | const r = sub(ray, muli(n1, 2.0*dot(ray, n1))); 369 | c = add(c, muli(raycolor(add(p, muli(r,EPS)), r, EPS, SENTINEL, depth-1), m.mirror)); 370 | } 371 | } 372 | return c; 373 | } 374 | return background; 375 | } 376 | 377 | function fail(msg) { 378 | PRINT(""); 379 | PRINT(msg); 380 | throw new Error("EXIT"); 381 | } 382 | -------------------------------------------------------------------------------- /demo/ray/README.md: -------------------------------------------------------------------------------- 1 | Ray tracer - sequential code. 2 | 3 | expected-output.ppm is the expected output with all features turned on. 4 | -------------------------------------------------------------------------------- /demo/ray/expected-output.ppm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lars-t-hansen/flatjs/8e23cacffe606cb5e20fd34fbfc468cc5c07e071/demo/ray/expected-output.ppm -------------------------------------------------------------------------------- /demo/ray/ray.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 |
7 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/ray/ray.js: -------------------------------------------------------------------------------- 1 | // Ray tracer, largely out of Shirley & Marschner 3rd Ed. 2 | // Traces a scene and writes to a canvas. 3 | // 4 | // lth@acm.org / lhansen@mozilla.com, winter 2012 and later. 5 | // 6 | // The language is straight Javascript and runs properly in Firefox, 7 | // Safari, and Chrome. 8 | 9 | // CONFIGURATION 10 | 11 | const shadows = true; // Compute object shadows 12 | const reflection = true; // Compute object reflections 13 | const reflection_depth = 2; 14 | const antialias = false; // true; // Antialias the image (expensive but pretty) 15 | 16 | // END CONFIGURATION 17 | 18 | const debug = false; // Progress printout, may confuse the consumer 19 | 20 | const SENTINEL = 1e32; 21 | const EPS = 0.00001; 22 | 23 | function DL3(x, y, z) { return {x:x, y:y, z:z}; } 24 | 25 | function add(a, b) { return DL3(a.x+b.x, a.y+b.y, a.z+b.z); } 26 | function addi(a, c) { return DL3(a.x+c, a.y+c, a.z+c); } 27 | function sub(a, b) { return DL3(a.x-b.x, a.y-b.y, a.z-b.z); } 28 | function subi(a, c) { return DL3(a.x-c, a.y-c, a.z-c); } 29 | function muli(a, c) { return DL3(a.x*c, a.y*c, a.z*c); } 30 | function divi(a, c) { return DL3(a.x/c, a.y/c, a.z/c); } 31 | function neg(a) { return DL3(-a.x, -a.y, -a.z); } 32 | function length(a) { return Math.sqrt(a.x*a.x + a.y*a.y + a.z*a.z); } 33 | function normalize(a) { var d = length(a); return DL3(a.x/d, a.y/d, a.z/d); } 34 | function cross(a, b) { return DL3(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x); } 35 | function dot(a, b) { return a.x*b.x + a.y*b.y + a.z*b.z; } 36 | 37 | function Material(diffuse, specular, shininess, ambient, mirror) { 38 | this.diffuse = diffuse; 39 | this.specular = specular; 40 | this.shininess = shininess; 41 | this.ambient = ambient; 42 | this.mirror = mirror; 43 | } 44 | 45 | const zzz = DL3(0,0,0); 46 | const m0 = new Material(zzz, zzz, 0, zzz, 0); 47 | 48 | function Scene() { 49 | this.material = m0; 50 | this.objects = []; 51 | } 52 | 53 | Scene.prototype.add = 54 | function(obj) { 55 | this.objects.push(obj); 56 | }; 57 | 58 | Scene.prototype.intersect = 59 | function (eye, ray, min, max) { 60 | var min_obj = null; 61 | var min_dist = SENTINEL; 62 | 63 | var objs = this.objects; 64 | for ( var idx=0, limit=objs.length ; idx < limit ; idx++ ) { 65 | var surf = objs[idx]; 66 | var tmp = surf.intersect(eye, ray, min, max); 67 | var obj = tmp.obj; 68 | var dist = tmp.dist; 69 | if (obj) 70 | if (dist >= min && dist < max) 71 | if (dist < min_dist) { 72 | min_obj = obj; 73 | min_dist = dist; 74 | } 75 | } 76 | return {obj:min_obj, dist:min_dist}; 77 | }; 78 | 79 | Scene.prototype.normal = 80 | function (p) { 81 | fail("normal() not defined on Scene"); 82 | return zzz; 83 | }; 84 | 85 | function Sphere(material, center, radius) { 86 | this.material = material; 87 | this.center = center; 88 | this.radius = radius; 89 | } 90 | 91 | Sphere.prototype.intersect = 92 | function (eye, ray, min, max) { 93 | var DdotD = dot(ray, ray); 94 | var EminusC = sub(eye, this.center); 95 | var B = dot(ray, EminusC); 96 | var disc = B*B - DdotD*(dot(EminusC,EminusC) - this.radius*this.radius); 97 | if (disc < 0.0) 98 | return {obj:null, dist:0}; 99 | var s1 = (-B + Math.sqrt(disc))/DdotD; 100 | var s2 = (-B - Math.sqrt(disc))/DdotD; 101 | // Here return the smallest of s1 and s2 after filtering for _min and _max 102 | if (s1 < min || s1 > max) 103 | s1 = SENTINEL; 104 | if (s2 < min || s2 > max) 105 | s2 = SENTINEL; 106 | var _dist = Math.min(s1,s2); 107 | if (_dist == SENTINEL) 108 | return {obj:null, dist:0}; 109 | return {obj:this, dist:_dist}; 110 | }; 111 | 112 | Sphere.prototype.normal = 113 | function (p) { 114 | return divi(sub(p, this.center), this.radius); 115 | }; 116 | 117 | function Triangle(material, v1, v2, v3) { 118 | this.material = material; 119 | this.v1 = v1; 120 | this.v2 = v2; 121 | this.v3 = v3; 122 | } 123 | 124 | Triangle.prototype.intersect = 125 | function (eye, ray, min, max) { 126 | // TODO: observe that values that do not depend on g, h, and i can be precomputed 127 | // and stored with the triangle (for a given eye position), at some (possibly significant) 128 | // space cost. Notably the numerator of "t" is invariant, as are many factors of the 129 | // numerator of "gamma". 130 | var v1 = this.v1; 131 | var v2 = this.v2; 132 | var v3 = this.v3; 133 | var a = v1.x - v2.x; 134 | var b = v1.y - v2.y; 135 | var c = v1.z - v2.z; 136 | var d = v1.x - v3.x; 137 | var e = v1.y - v3.y; 138 | var f = v1.z - v3.z; 139 | var g = ray.x; 140 | var h = ray.y; 141 | var i = ray.z; 142 | var j = v1.x - eye.x; 143 | var k = v1.y - eye.y; 144 | var l = v1.z - eye.z; 145 | var M = a*(e*i - h*f) + b*(g*f - d*i) + c*(d*h - e*g); 146 | var t = -((f*(a*k - j*b) + e*(j*c - a*l) + d*(b*l - k*c))/M); 147 | if (t < min || t > max) 148 | return {obj:null,dist:0}; 149 | var gamma = (i*(a*k - j*b) + h*(j*c - a*l) + g*(b*l - k*c))/M; 150 | if (gamma < 0 || gamma > 1.0) 151 | return {obj:null,dist:0}; 152 | var beta = (j*(e*i - h*f) + k*(g*f - d*i) + l*(d*h - e*g))/M; 153 | if (beta < 0.0 || beta > 1.0 - gamma) 154 | return {obj:null,dist:0}; 155 | return {obj:this, dist:t}; 156 | }; 157 | 158 | Triangle.prototype.normal = 159 | function (p) { 160 | // TODO: Observe that the normal is invariant and can be stored with the triangle 161 | return normalize(cross(sub(this.v2, this.v1), sub(this.v3, this.v1))); 162 | }; 163 | 164 | function Bitmap(height, width, color) { 165 | this.height = height; 166 | this.width = width; 167 | var c = (255<<24)|((255*color.z)<<16)|((255*color.y)<<8)|(255*color.x) 168 | var b = new Int32Array(width*height); 169 | this.data = b; 170 | for ( var i=0, l=width*height ; i < l ; i++ ) 171 | b[i] = c; 172 | } 173 | 174 | // For debugging only 175 | Bitmap.prototype.ref = 176 | function (y, x) { 177 | return this.data[(this.height-y)*this.width+x]; 178 | }; 179 | 180 | // Not a hot function 181 | Bitmap.prototype.setColor = 182 | function (y, x, v) { 183 | this.data[(this.height-y-1)*this.width+x] = (255<<24)|((255*v.z)<<16)|((255*v.y)<<8)|(255*v.x); 184 | }; 185 | 186 | const height = 600; 187 | const width = 800; 188 | 189 | const g_left = -2; 190 | const g_right = 2; 191 | const g_top = 1.5; 192 | const g_bottom = -1.5; 193 | 194 | const bits = new Bitmap(height, width, DL3(152.0/256.0, 251.0/256.0, 152.0/256.0)); 195 | 196 | function main() { 197 | setStage(); 198 | var then = Date.now(); 199 | trace(0, height); 200 | var now = Date.now(); 201 | 202 | var mycanvas = document.getElementById("mycanvas"); 203 | var cx = mycanvas.getContext('2d'); 204 | var id = cx.createImageData(width, height); 205 | id.data.set(new Uint8Array(bits.data.buffer)); 206 | cx.putImageData( id, 0, 0 ); 207 | document.getElementById("mycaption").innerHTML = "Time=" + (now - then) + "ms"; 208 | 209 | return 0; 210 | } 211 | 212 | var eye = zzz; // Eye coordinates 213 | var light = zzz; // Light source coordinates 214 | var background = zzz; // Background color 215 | var world = new Scene(); 216 | 217 | // Colors: http://kb.iu.edu/data/aetf.html 218 | 219 | const paleGreen = DL3(152.0/256.0, 251.0/256.0, 152.0/256.0); 220 | const darkGray = DL3(169.0/256.0, 169.0/256.0, 169.0/256.0); 221 | const yellow = DL3(1.0, 1.0, 0.0); 222 | const red = DL3(1.0, 0.0, 0.0); 223 | const blue = DL3(0.0, 0.0, 1.0); 224 | 225 | // Not restricted to a rectangle, actually 226 | function rectangle(m, v1, v2, v3, v4) { 227 | world.add(new Triangle(m, v1, v2, v3)); 228 | world.add(new Triangle(m, v1, v3, v4)); 229 | } 230 | 231 | // Vertices are for front and back faces, both counterclockwise as seen 232 | // from the outside. 233 | // Not restricted to a cube, actually. 234 | function cube(m, v1, v2, v3, v4, v5, v6, v7, v8) { 235 | rectangle(m, v1, v2, v3, v4); // front 236 | rectangle(m, v2, v5, v8, v3); // right 237 | rectangle(m, v6, v1, v4, v7); // left 238 | rectangle(m, v5, v5, v7, v8); // back 239 | rectangle(m, v4, v3, v8, v7); // top 240 | rectangle(m, v6, v5, v2, v1); // bottom 241 | } 242 | 243 | function setStage() { 244 | if (debug) 245 | PRINT("Setstage start"); 246 | const m1 = new Material(DL3(0.1, 0.2, 0.2), DL3(0.3, 0.6, 0.6), 10, DL3(0.05, 0.1, 0.1), 0); 247 | const m2 = new Material(DL3(0.3, 0.3, 0.2), DL3(0.6, 0.6, 0.4), 10, DL3(0.1,0.1,0.05), 0); 248 | const m3 = new Material(DL3(0.1, 0, 0), DL3(0.8,0,0), 10, DL3(0.1,0,0), 0); 249 | const m4 = new Material(muli(darkGray,0.4), muli(darkGray,0.3), 100, muli(darkGray,0.3), 0.5); 250 | const m5 = new Material(muli(paleGreen,0.4), muli(paleGreen,0.4), 10, muli(paleGreen,0.2), 1.0); 251 | const m6 = new Material(muli(yellow,0.6), zzz, 0, muli(yellow,0.4), 0); 252 | const m7 = new Material(muli(red,0.6), zzz, 0, muli(red,0.4), 0); 253 | const m8 = new Material(muli(blue,0.6), zzz, 0, muli(blue,0.4), 0); 254 | 255 | world.add(new Sphere(m1, DL3(-1, 1, -9), 1)); 256 | world.add(new Sphere(m2, DL3(1.5, 1, 0), 0.75)); 257 | world.add(new Triangle(m1, DL3(-1,0,0.75), DL3(-0.75,0,0), DL3(-0.75,1.5,0))); 258 | world.add(new Triangle(m3, DL3(-2,0,0), DL3(-0.5,0,0), DL3(-0.5,2,0))); 259 | rectangle(m4, DL3(-5,0,5), DL3(5,0,5), DL3(5,0,-40), DL3(-5,0,-40)); 260 | cube(m5, DL3(1, 1.5, 1.5), DL3(1.5, 1.5, 1.25), DL3(1.5, 1.75, 1.25), DL3(1, 1.75, 1.5), 261 | DL3(1.5, 1.5, 0.5), DL3(1, 1.5, 0.75), DL3(1, 1.75, 0.75), DL3(1.5, 1.75, 0.5)); 262 | for ( var i=0 ; i < 30 ; i++ ) 263 | world.add(new Sphere(m6, DL3((-0.6+(i*0.2)), (0.075+(i*0.05)), (1.5-(i*Math.cos(i/30.0)*0.5))), 0.075)); 264 | for ( var i=0 ; i < 60 ; i++ ) 265 | world.add(new Sphere(m7, DL3((1+0.3*Math.sin(i*(3.14/16))), (0.075+(i*0.025)), (1+0.3*Math.cos(i*(3.14/16)))), 0.025)); 266 | for ( var i=0 ; i < 60 ; i++ ) 267 | world.add(new Sphere(m8, DL3((1+0.3*Math.sin(i*(3.14/16))), (0.075+((i+8)*0.025)), (1+0.3*Math.cos(i*(3.14/16)))), 0.025)); 268 | 269 | eye = DL3(0.5, 0.75, 5); 270 | light = DL3(g_left-1, g_top, 2); 271 | background = DL3(25.0/256.0,25.0/256.0,112.0/256.0); 272 | if (debug) 273 | PRINT("Setstage end"); 274 | } 275 | 276 | function trace(hmin, hlim) { 277 | if (antialias) 278 | traceWithAntialias(hmin, hlim); 279 | else 280 | traceWithoutAntialias(hmin, hlim); 281 | } 282 | 283 | function traceWithoutAntialias(hmin, hlim) { 284 | for ( var h=hmin ; h < hlim ; h++ ) { 285 | if (debug) 286 | PRINT("Row " + h); 287 | for ( var w=0 ; w < width ; w++ ) { 288 | var u = g_left + (g_right - g_left)*(w + 0.5)/width; 289 | var v = g_bottom + (g_top - g_bottom)*(h + 0.5)/height; 290 | var ray = DL3(u, v, -eye.z); 291 | var col = raycolor(eye, ray, 0, SENTINEL, reflection_depth); 292 | bits.setColor(h, w, col); 293 | } 294 | } 295 | } 296 | 297 | const random_numbers = [ 298 | 0.495,0.840,0.636,0.407,0.026,0.547,0.223,0.349,0.033,0.643,0.558,0.481,0.039, 299 | 0.175,0.169,0.606,0.638,0.364,0.709,0.814,0.206,0.346,0.812,0.603,0.969,0.888, 300 | 0.294,0.824,0.410,0.467,0.029,0.706,0.314 301 | ]; 302 | 303 | function traceWithAntialias(hmin, hlim) { 304 | var k = 0; 305 | for ( var h=hmin ; h < hlim ; h++ ) { 306 | //if (debug) 307 | // PRINT("Row " + h); 308 | for ( var w=0 ; w < width ; w++ ) { 309 | // Simple stratified sampling, cf Shirley&Marschner ch 13 and a fast "random" function. 310 | const n = 4; 311 | //var k = h % 32; 312 | var rand = k % 2; 313 | var c = zzz; 314 | k++; 315 | for ( var p=0 ; p < n ; p++ ) { 316 | for ( var q=0 ; q < n ; q++ ) { 317 | var jx = random_numbers[rand]; rand=rand+1; 318 | var jy = random_numbers[rand]; rand=rand+1; 319 | var u = g_left + (g_right - g_left)*(w + (p + jx)/n)/width; 320 | var v = g_bottom + (g_top - g_bottom)*(h + (q + jy)/n)/height; 321 | var ray = DL3(u, v, -eye.z); 322 | c = add(c, raycolor(eye, ray, 0.0, SENTINEL, reflection_depth)); 323 | } 324 | } 325 | bits.setColor(h,w,divi(c,n*n)); 326 | } 327 | } 328 | } 329 | 330 | // Clamping c is not necessary provided the three color components by 331 | // themselves never add up to more than 1, and shininess == 0 or shininess >= 1. 332 | // 333 | // TODO: lighting intensity is baked into the material here, but we probably want 334 | // to factor that out and somehow attenuate light with distance from the light source, 335 | // for diffuse and specular lighting. 336 | 337 | function raycolor(eye, ray, t0, t1, depth) { 338 | var tmp = world.intersect(eye, ray, t0, t1); 339 | var obj = tmp.obj; 340 | var dist = tmp.dist; 341 | 342 | if (obj) { 343 | const m = obj.material; 344 | const p = add(eye, muli(ray, dist)); 345 | const n1 = obj.normal(p); 346 | const l1 = normalize(sub(light, p)); 347 | var c = m.ambient; 348 | var min_obj = null; 349 | 350 | // Passing NULL here and testing for it in intersect() was intended as an optimization, 351 | // since any hit will do, but does not seem to have much of an effect in scenes tested 352 | // so far - maybe not enough scene detail (too few shadows). 353 | if (shadows) { 354 | var tmp = world.intersect(add(p, muli(l1, EPS)), l1, EPS, SENTINEL); 355 | min_obj = tmp.obj; 356 | } 357 | if (!min_obj) { 358 | const diffuse = Math.max(0.0, dot(n1,l1)); 359 | const v1 = normalize(neg(ray)); 360 | const h1 = normalize(add(v1, l1)); 361 | const specular = Math.pow(Math.max(0.0, dot(n1, h1)), m.shininess); 362 | c = add(c, add(muli(m.diffuse,diffuse), muli(m.specular,specular))); 363 | if (reflection) 364 | if (depth > 0.0 && m.mirror != 0.0) { 365 | const r = sub(ray, muli(n1, 2.0*dot(ray, n1))); 366 | c = add(c, muli(raycolor(add(p, muli(r,EPS)), r, EPS, SENTINEL, depth-1), m.mirror)); 367 | } 368 | } 369 | return c; 370 | } 371 | return background; 372 | } 373 | 374 | function fail(msg) { 375 | PRINT(""); 376 | PRINT(msg); 377 | throw new Error("EXIT"); 378 | } 379 | -------------------------------------------------------------------------------- /fjsc.ts: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript; electric-indent-local-mode: nil -*- */ 2 | /* This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | * 6 | * Author: Lars T Hansen, lhansen@mozilla.com 7 | */ 8 | 9 | /* 10 | * FlatJS compiler. Desugars FlatJS syntax in JavaScript programs. 11 | * 12 | * Usage: 13 | * fjsc input-file ... 14 | * 15 | * One output file will be produced for each input file. Each input 16 | * file must have extension .flat_xx where xx is typically js or ts. 17 | * On output the ".flat" qualifier will be stripped. 18 | * 19 | * --- 20 | * 21 | * This is source code for TypeScript 1.5 and node.js 0.10 / ECMAScript 5. 22 | * Tested with tsc 1.5.0-beta and nodejs 0.10.25 and 0.12.0, on Linux and Mac OS X. 23 | */ 24 | 25 | /// 26 | 27 | import fs = require("fs"); 28 | 29 | const VERSION = "0.5"; 30 | 31 | enum DefnKind { 32 | Class, 33 | Struct, 34 | Primitive 35 | } 36 | 37 | class Defn { 38 | size = 0; 39 | align = 0; 40 | 41 | constructor(public name:string, public kind:DefnKind) { } 42 | 43 | get elementSize(): number { return this.size; } 44 | get elementAlign(): number { return this.align; } 45 | 46 | static pointerSize = 4; 47 | static pointerAlign = 4; 48 | static pointerTypeName = "int32"; 49 | static pointerMemName = "_mem_int32"; 50 | } 51 | 52 | enum PrimKind { 53 | Vanilla, 54 | Atomic, 55 | Synchronic, 56 | SIMD 57 | } 58 | 59 | class PrimitiveDefn extends Defn { 60 | private _memory: string; 61 | 62 | constructor(name:string, size:number, align:number, public primKind:PrimKind=PrimKind.Vanilla) { 63 | super(name, DefnKind.Primitive); 64 | this.size = size; 65 | this.align = align; 66 | if (primKind == PrimKind.SIMD) 67 | this._memory = "_mem_" + name.split("x")[0]; 68 | else 69 | this._memory = "_mem_" + name.split("/").pop(); 70 | } 71 | 72 | get memory(): string { 73 | return this._memory; 74 | } 75 | } 76 | 77 | class AtomicDefn extends PrimitiveDefn { 78 | constructor(name:string, size:number, align:number) { 79 | super(name, size, align, PrimKind.Atomic); 80 | } 81 | } 82 | 83 | class SynchronicDefn extends PrimitiveDefn { 84 | constructor(name:string, size:number, align:number, public baseSize:number) { 85 | super(name, size, align, PrimKind.Synchronic); 86 | } 87 | 88 | // The byte offset within the structure for the payload 89 | static bias = 8; 90 | } 91 | 92 | class SIMDDefn extends PrimitiveDefn { 93 | constructor(name:string, size:number, align:number, public baseSize:number) { 94 | super(name, size, align, PrimKind.SIMD); 95 | } 96 | } 97 | 98 | class UserDefn extends Defn { 99 | typeRef: StructDefn = null; 100 | map: SMap = null; 101 | live = false; 102 | checked = false; 103 | 104 | constructor(public file:string, public line:number, name:string, kind:DefnKind, public props:Prop[], 105 | public methods:Method[], public origin:number) 106 | { 107 | super(name, kind); 108 | } 109 | 110 | findAccessibleFieldFor(operation:string, prop:string):MapEntry { 111 | let d = this.map.get(prop); 112 | if (!d) 113 | return null; 114 | switch (operation) { 115 | case "get": 116 | case "set": 117 | case "ref": 118 | return d; 119 | case "add": 120 | case "sub": 121 | case "and": 122 | case "or": 123 | case "xor": 124 | case "compareExchange": { 125 | if (d.type.kind != DefnKind.Primitive) 126 | return null; 127 | let prim = d.type; 128 | // add, sub, and, or, and xor are defined on plain primitives too, for 129 | // internal reasons, but that is not documented. 130 | //if (prim.primKind != PrimKind.Atomic && prim.primKind != PrimKind.Synchronic) 131 | // return null; 132 | return d; 133 | } 134 | case "loadWhenEqual": 135 | case "loadWhenNotEqual": 136 | case "expectUpdate": 137 | case "notify": { 138 | if (d.type.kind != DefnKind.Primitive) 139 | return null; 140 | let prim = d.type; 141 | if (prim.primKind != PrimKind.Synchronic) 142 | return null; 143 | return d; 144 | } 145 | default: 146 | return null; 147 | } 148 | } 149 | } 150 | 151 | class ClassDefn extends UserDefn { 152 | baseTypeRef: ClassDefn = null; 153 | className = ""; // Base1>Base2>name 154 | classId = 0; 155 | subclasses: ClassDefn[] = []; // direct proper subclasses 156 | vtable:Virtual[] = null; 157 | 158 | constructor(file:string, line:number, name:string, public baseName:string, props:Prop[], methods:Method[], origin:number) { 159 | super(file, line, name, DefnKind.Class, props, methods, origin); 160 | } 161 | 162 | get elementSize(): number { return Defn.pointerSize; } 163 | get elementAlign(): number { return Defn.pointerAlign; } 164 | 165 | hasMethod(name:string):boolean { 166 | for ( let m of this.methods ) 167 | if (m.name == name) 168 | return true; 169 | return false; 170 | } 171 | 172 | getMethod(name:string):Method { 173 | for ( let m of this.methods ) 174 | if (m.name == name) 175 | return m; 176 | return null; 177 | } 178 | } 179 | 180 | class Virtual { 181 | constructor(public name:string, private sign:string[], public reverseCases: SMap, public default_:string) {} 182 | 183 | signature():string { 184 | if (this.sign == null) 185 | return ", ...args"; 186 | if (this.sign.length == 0) 187 | return ""; 188 | return ", " + this.sign.join(","); 189 | } 190 | } 191 | 192 | class VirtualMethodIterator { 193 | private i = 0; 194 | private inherited = false; 195 | private filter = new SSet(); 196 | 197 | constructor(private cls:ClassDefn) {} 198 | 199 | next(): [string,string[],boolean] { 200 | for (;;) { 201 | if (this.i == this.cls.methods.length) { 202 | if (!this.cls.baseTypeRef) 203 | return ["", null, false]; 204 | this.i = 0; 205 | this.cls = this.cls.baseTypeRef; 206 | this.inherited = true; 207 | continue; 208 | } 209 | let m = this.cls.methods[this.i++]; 210 | if (m.kind != MethodKind.Virtual) 211 | continue; 212 | if (this.filter.test(m.name)) 213 | continue; 214 | this.filter.put(m.name); 215 | return [m.name, m.signature, this.inherited]; 216 | } 217 | } 218 | } 219 | 220 | class InclusiveSubclassIterator { 221 | private stack:(ClassDefn|number|ClassDefn[])[] = []; 222 | 223 | constructor(cls:ClassDefn) { 224 | this.stack.push(cls); 225 | } 226 | 227 | next(): ClassDefn { 228 | if (this.stack.length == 0) 229 | return null; 230 | let top = this.stack.pop(); 231 | if (typeof top == "number") { 232 | let x = top; 233 | let xs = this.stack.pop(); 234 | let cls = xs[x++]; 235 | if (x < xs.length) { 236 | this.stack.push(xs); 237 | this.stack.push(x); 238 | } 239 | if (cls.subclasses.length > 0) { 240 | this.stack.push(cls.subclasses); 241 | this.stack.push(0); 242 | } 243 | return cls; 244 | } 245 | else { 246 | let x = top; 247 | if (x.subclasses.length > 0) { 248 | this.stack.push(x.subclasses); 249 | this.stack.push(0); 250 | } 251 | return x; 252 | } 253 | } 254 | } 255 | 256 | class StructDefn extends UserDefn { 257 | hasGetMethod = false; 258 | hasSetMethod = false; 259 | 260 | constructor(file:string, line:number, name:string, props:Prop[], methods:Method[], origin:number) { 261 | super(file, line, name, DefnKind.Struct, props, methods, origin); 262 | for ( let m of methods ) { 263 | if (m.kind == MethodKind.Get) 264 | this.hasGetMethod = true; 265 | else if (m.kind == MethodKind.Set) 266 | this.hasSetMethod = true; 267 | } 268 | } 269 | } 270 | 271 | enum PropQual { 272 | None, 273 | Atomic, 274 | Synchronic 275 | } 276 | 277 | class Prop { 278 | typeRef: Defn = null; 279 | 280 | constructor(public line:number, public name:string, public qual:PropQual, public isArray:boolean, public typeName:string) {} 281 | } 282 | 283 | enum MethodKind { 284 | Virtual, 285 | NonVirtual, 286 | Get, 287 | Set 288 | } 289 | 290 | class Method { 291 | constructor(public line:number, public kind:MethodKind, public name:string, public signature:string[], public body: string[]) {} 292 | } 293 | 294 | class MapEntry { 295 | constructor(public name:string, public expand:boolean, public offset:number, public type:Defn) {} 296 | 297 | get memory(): string { 298 | if (this.type.kind != DefnKind.Primitive) 299 | throw new InternalError("No memory type available for non-primitive type " + this.type.name); 300 | return ( this.type).memory; 301 | } 302 | 303 | get size(): number { 304 | return this.type.size; 305 | } 306 | 307 | toString():string { 308 | return "(" + this.name + " " + this.expand + " " + this.offset + " " + this.type.name + ")"; 309 | } 310 | } 311 | 312 | // Simple map from string to T. Allows properties to be added and 313 | // updated, but not to be removed. 314 | 315 | class SMap { 316 | private props: {name:string, value:T}[] = []; 317 | private mapping = {}; // Map from name to index 318 | private generation = 0; // Incremented on update (but not on add) 319 | 320 | test(n:string):boolean { 321 | return typeof this.mapping[n] == "number"; 322 | } 323 | 324 | get(n:string):T { 325 | let probe = this.mapping[n]; 326 | if (typeof probe == "number") 327 | return this.props[probe].value; 328 | return null; 329 | } 330 | 331 | put(n:string, v:T):void { 332 | let probe = this.mapping[n]; 333 | if (typeof probe == "number") { 334 | this.props[probe].value = v; 335 | this.generation++; 336 | } 337 | else { 338 | this.mapping[n] = this.props.length; 339 | this.props.push({name:n, value:v}); 340 | } 341 | } 342 | 343 | copy(): SMap { 344 | let newMap = new SMap(); 345 | newMap.props = this.props.slice(0); 346 | for ( let n in this.mapping ) 347 | if (this.mapping.hasOwnProperty(n)) 348 | newMap.mapping[n] = this.mapping[n]; 349 | return newMap; 350 | } 351 | 352 | values(): { next: () => T } { 353 | const theMap = this; 354 | const generation = this.generation; 355 | const props = this.props; 356 | let i = 0; 357 | return { next: 358 | function (): T { 359 | if (theMap.generation != generation) 360 | throw new InternalError("Generator invalidated by assignment"); 361 | if (i == props.length) 362 | return null; 363 | return props[i++].value; 364 | } }; 365 | } 366 | 367 | keysValues(): { next: () => [string,T] } { 368 | const theMap = this; 369 | const generation = this.generation; 370 | const props = this.props; 371 | let i = 0; 372 | return { next: 373 | function (): [string,T] { 374 | if (theMap.generation != generation) 375 | throw new InternalError("Generator invalidated by assignment"); 376 | if (i == props.length) 377 | return [null,null]; 378 | let x = props[i++]; 379 | return [x.name,x.value]; 380 | } }; 381 | } 382 | } 383 | 384 | // String set 385 | 386 | class SSet { 387 | private mapping = {}; // Map from name to true 388 | 389 | test(n:string):boolean { 390 | return typeof this.mapping[n] == "boolean"; 391 | } 392 | 393 | put(n:string):void { 394 | this.mapping[n] = true; 395 | } 396 | } 397 | 398 | class SourceLine { 399 | constructor(public file:string, public line:number, public text:string) {} 400 | } 401 | 402 | class Source { 403 | constructor(public input_file:string, public output_file:string, public defs:UserDefn[], public lines:SourceLine[]) {} 404 | 405 | allText(): string { 406 | return this.lines.map(function (x) { return x.text }).join("\n"); 407 | } 408 | } 409 | 410 | class CapturedError { 411 | constructor(public name:string, public message:string) {} 412 | } 413 | 414 | class InternalError extends CapturedError { 415 | constructor(msg:string) { 416 | super("InternalError", "Internal error: " + msg); 417 | } 418 | } 419 | 420 | class UsageError extends CapturedError { 421 | constructor(msg:string) { 422 | super("UsageError", "Usage error: " + msg); 423 | } 424 | } 425 | 426 | class ProgramError extends CapturedError { 427 | constructor(file:string, line:number, msg:string) { 428 | super("ProgramError", file + ":" + line + ": " + msg); 429 | } 430 | } 431 | 432 | const allSources:Source[] = []; 433 | 434 | function main(args: string[]):void { 435 | try { 436 | for ( let input_file of args ) { 437 | if (!(/.\.flat_[a-zA-Z0-9]+$/.test(input_file))) 438 | throw new UsageError("Bad file name (must be *.flat_): " + input_file); 439 | let text = fs.readFileSync(input_file, "utf8"); 440 | let lines = text.split("\n"); 441 | let [defs, residual] = collectDefinitions(input_file, lines); 442 | let output_file = input_file.replace(/\.flat_([a-zA-Z0-9]+)$/, ".$1"); 443 | allSources.push(new Source(input_file, output_file, defs, residual)); 444 | } 445 | 446 | buildTypeMap(); 447 | resolveTypeRefs(); 448 | checkRecursion(); 449 | checkMethods(); 450 | layoutTypes(); 451 | createVirtuals(); 452 | 453 | expandSelfAccessors(); 454 | pasteupTypes(); 455 | expandGlobalAccessorsAndMacros(); 456 | 457 | for ( let s of allSources ) { 458 | fs.writeFileSync(s.output_file, 459 | "// Generated from " + s.input_file + " by fjsc " + VERSION + "; github.com/lars-t-hansen/flatjs\n" + s.allText(), 460 | "utf8"); 461 | } 462 | } 463 | catch (e) { 464 | if (e instanceof CapturedError) 465 | console.log(e.message); 466 | else 467 | console.log(e); 468 | process.exit(1); 469 | } 470 | } 471 | 472 | function log2(x:number):number { 473 | if (x <= 0) 474 | throw new InternalError("log2: " + x); 475 | let i = 0; 476 | while (x > 1) { 477 | i++; 478 | x >>= 1; 479 | } 480 | return i; 481 | } 482 | 483 | function warning(file:string, line:number, msg:string):void { 484 | console.log(file + ":" + line + ": Warning: " + msg); 485 | } 486 | 487 | ////////////////////////////////////////////////////////////////////////////////////////// 488 | // 489 | // Parsing 490 | 491 | const Ws = "\\s+"; 492 | const Os = "\\s*"; 493 | const Id = "[A-Za-z][A-Za-z0-9]*"; // Note, no underscores are allowed yet 494 | const Lbrace = Os + "\\{"; 495 | const Rbrace = Os + "\\}"; 496 | const LParen = Os + "\\("; 497 | const CommentOpt = Os + "(?:\\/\\/.*)?"; 498 | const QualifierOpt = "(?:\\.(atomic|synchronic))?" 499 | const OpNames = "at|get|setAt|set|ref|add|sub|and|or|xor|compareExchange|loadWhenEqual|loadWhenNotEqual|expectUpdate|notify"; 500 | const Operation = "(?:\\.(" + OpNames + "))"; 501 | const OperationOpt = Operation + "?"; 502 | const OperationLParen = "(?:\\.(" + OpNames + ")" + LParen + ")"; 503 | const NullaryOperation = "(?:\\.(ref|notify))"; 504 | const Path = "((?:\\." + Id + ")+)"; 505 | const PathLazy = "((?:\\." + Id + ")+?)"; 506 | const PathOpt = "((?:\\." + Id + ")*)"; 507 | const PathOptLazy = "((?:\\." + Id + ")*?)"; 508 | const AssignOp = "(=|\\+=|-=|&=|\\|=|\\^=)(?!=)"; 509 | 510 | const start_re = new RegExp("^" + Os + "@flatjs" + Ws + "(?:struct|class)" + Ws + "(?:" + Id + ")"); 511 | const end_re = new RegExp("^" + Rbrace + Os + "@end" + CommentOpt + "$"); 512 | const struct_re = new RegExp("^" + Os + "@flatjs" + Ws + "struct" + Ws + "(" + Id + ")" + Lbrace + CommentOpt + "$"); 513 | const class_re = new RegExp("^" + Os + "@flatjs" + Ws + "class" + Ws + "(" + Id + ")" + Os + "(?:extends" + Ws + "(" + Id + "))?" + Lbrace + CommentOpt + "$"); 514 | const special_re = new RegExp("^" + Os + "@(get|set)" + "(" + LParen + Os + "SELF.*)$"); 515 | const method_re = new RegExp("^" + Os + "@(method|virtual)" + Ws + "(" + Id + ")" + "(" + LParen + Os + "SELF.*)$"); 516 | const blank_re = new RegExp("^" + Os + CommentOpt + "$"); 517 | const space_re = new RegExp("^" + Os + "$"); 518 | const prop_re = new RegExp("^" + Os + "(" + Id + ")" + Os + ":" + Os + "(" + Id + ")" + QualifierOpt + "(?:\.(Array))?" + Os + ";?" + CommentOpt + "$"); 519 | 520 | function collectDefinitions(filename:string, lines:string[]):[UserDefn[], SourceLine[]] { 521 | let defs:UserDefn[] = []; 522 | let nlines:SourceLine[] = []; 523 | let i=0, lim=lines.length; 524 | while (i < lim) { 525 | let l = lines[i++]; 526 | if (!start_re.test(l)) { 527 | nlines.push(new SourceLine(filename, i, l)); 528 | continue; 529 | } 530 | 531 | let kind = ""; 532 | let name = ""; 533 | let inherit = ""; 534 | let lineno = i; 535 | let m:string[] = null; 536 | if (m = struct_re.exec(l)) { 537 | kind = "struct"; 538 | name = m[1]; 539 | } 540 | else if (m = class_re.exec(l)) { 541 | kind = "class"; 542 | name = m[1]; 543 | inherit = m[2] ? m[2] : ""; 544 | } 545 | else 546 | throw new ProgramError(filename, i, "Syntax error: Malformed definition line"); 547 | 548 | let properties:Prop[] = []; 549 | let methods:Method[] = []; 550 | let in_method = false; 551 | let mbody:string[] = null; 552 | let method_type = MethodKind.Virtual; 553 | let method_name = ""; 554 | let method_line = 0; 555 | let method_signature:string[] = null; 556 | 557 | // Do not check for duplicate names here since that needs to 558 | // take into account inheritance. 559 | 560 | while (i < lim) { 561 | l = lines[i++]; 562 | if (end_re.test(l)) 563 | break; 564 | if (m = method_re.exec(l)) { 565 | if (kind != "class") 566 | throw new ProgramError(filename, i, "@method is only allowed in classes"); 567 | if (in_method) 568 | methods.push(new Method(method_line, method_type, method_name, method_signature, mbody)); 569 | in_method = true; 570 | method_line = i; 571 | method_type = (m[1] == "method" ? MethodKind.NonVirtual : MethodKind.Virtual); 572 | method_name = m[2]; 573 | // Parse the signature. Just use the param parser for now, 574 | // but note that what we get back will need postprocessing. 575 | let pp = new ParamParser(filename, i, m[3], /* skip left paren */ 1); 576 | let args = pp.allArgs(); 577 | args.shift(); // Discard SELF 578 | // Issue #15: In principle there are two signatures here: there is the 579 | // parameter signature, which we should keep intact in the 580 | // virtual, and there is the set of arguments extracted from that, 581 | // including any splat. 582 | method_signature = args.map(function (x) { return parameterToArgument(filename, i, x) }); 583 | mbody = [m[3]]; 584 | } 585 | else if (m = special_re.exec(l)) { 586 | if (kind != "struct") 587 | throw new ProgramError(filename, i, `@${m[1]} is only allowed in structs`); 588 | if (in_method) 589 | methods.push(new Method(method_line, method_type, method_name, method_signature, mbody)); 590 | method_line = i; 591 | in_method = true; 592 | switch (m[1]) { 593 | case "get": method_type = MethodKind.Get; break; 594 | case "set": method_type = MethodKind.Set; break; 595 | } 596 | method_name = ""; 597 | method_signature = null; 598 | mbody = [m[2]]; 599 | } 600 | else if (in_method) { 601 | // TODO: if we're going to be collecting random cruft 602 | // then blank and comment lines at the end of a method 603 | // really should be placed at the beginning of the 604 | // next method. Also see hack in pasteupTypes() that 605 | // removes blank lines from the end of a method body. 606 | mbody.push(l); 607 | } 608 | else if (m = prop_re.exec(l)) { 609 | let qual = PropQual.None; 610 | switch (m[3]) { 611 | case "synchronic": qual = PropQual.Synchronic; break; 612 | case "atomic": qual = PropQual.Atomic; break; 613 | } 614 | properties.push(new Prop(i, m[1], qual, m[4] == "Array", m[2])); 615 | } 616 | else if (blank_re.test(l)) { 617 | } 618 | else 619 | throw new ProgramError(filename, i, "Syntax error: Not a property or method: " + l); 620 | } 621 | if (in_method) 622 | methods.push(new Method(method_line, method_type, method_name, method_signature, mbody)); 623 | 624 | if (kind == "class") 625 | defs.push(new ClassDefn(filename, lineno, name, inherit, properties, methods, nlines.length)); 626 | else 627 | defs.push(new StructDefn(filename, lineno, name, properties, methods, nlines.length)); 628 | } 629 | return [defs, nlines]; 630 | } 631 | 632 | // The input is Id, Id:Blah, or ...Id. Strip any :Blah annotations. 633 | function parameterToArgument(file:string, line:number, s:string):string { 634 | if (/^\s*(?:\.\.\.)[A-Za-z_$][A-Za-z0-9_$]*\s*$/.test(s)) 635 | return s; 636 | let m = /^\s*([A-Za-z_\$][A-Za-z0-9_\$]*)\s*:?/.exec(s); 637 | if (!m) 638 | throw new ProgramError(file, line, "Unable to understand argument to virtual function: " + s); 639 | return m[1]; 640 | } 641 | 642 | 643 | class ParamParser { 644 | private lim = 0; 645 | private done = false; 646 | 647 | sawSemi = false; 648 | 649 | constructor(private file:string, private line:number, private input:string, private pos:number, 650 | private requireRightParen=true, private stopAtSemi=false) 651 | { 652 | this.lim = input.length; 653 | } 654 | 655 | // Returns null on failure to find a next argument 656 | nextArg():string { 657 | if (this.done) 658 | return null; 659 | let depth = 0; 660 | let start = this.pos; 661 | let sawRightParen = false; 662 | let sawComma = false; 663 | let fellOff = false; 664 | // Issue #8: Really should handle regular expressions, but much harder, and somewhat marginal 665 | loop: 666 | for (;;) { 667 | if (this.pos == this.lim) { 668 | this.done = true; 669 | fellOff = true; 670 | break loop; 671 | } 672 | switch (this.input.charAt(this.pos++)) { 673 | case '/': 674 | if (this.pos < this.lim && this.input.charAt(this.pos) == '/') { 675 | this.done = true; 676 | break loop; 677 | } 678 | if (this.pos < this.lim && this.input.charAt(this.pos) == '*') { 679 | this.pos++; 680 | for (;;) { 681 | if (this.pos == this.lim) 682 | throw new ProgramError(this.file, this.line, "Line ended unexpectedly - still nested within comment."); 683 | if (this.input.charAt(this.pos++) == '*' && this.pos < this.lim && this.input.charAt(this.pos) == '/') 684 | break; 685 | } 686 | } 687 | break; 688 | case ';': 689 | if (depth == 0 && this.stopAtSemi) { 690 | this.done = true; 691 | this.sawSemi = true; 692 | break loop; 693 | } 694 | break; 695 | case ',': 696 | if (depth == 0) { 697 | sawComma = true; 698 | break loop; 699 | } 700 | break; 701 | case '(': 702 | case '{': 703 | case '[': 704 | depth++; 705 | break; 706 | case '}': 707 | case ']': 708 | depth--; 709 | break; 710 | case ')': 711 | if (depth == 0) { 712 | this.done = true; 713 | sawRightParen = true; 714 | break loop; 715 | } 716 | depth--; 717 | break; 718 | case '\'': 719 | case '"': { 720 | let c = this.input.charAt(this.pos-1); 721 | for (;;) { 722 | if (this.pos == this.lim) 723 | throw new ProgramError(this.file, this.line, "Line ended unexpectedly - within a string."); 724 | let d = this.input.charAt(this.pos++); 725 | if (d == c) 726 | break; 727 | if (d == '\\') { 728 | if (this.pos < this.lim) 729 | this.pos++; 730 | } 731 | } 732 | break; 733 | } 734 | case '`': 735 | // Issue #25: Allow template strings 736 | throw new ProgramError(this.file, this.line, "Avoid template strings in arguments for now"); 737 | } 738 | } 739 | 740 | var result = this.cleanupArg(this.input.substring(start, fellOff ? this.pos : this.pos-1)); 741 | 742 | // Don't consume it if we don't know if we're going to find it. 743 | if (sawRightParen && !this.requireRightParen) 744 | this.pos--; 745 | 746 | if (this.done && depth > 0) 747 | throw new ProgramError(this.file, this.line, "Line ended unexpectedly - still nested within parentheses."); 748 | if (this.done && this.requireRightParen && !sawRightParen) 749 | throw new ProgramError(this.file, this.line, "Line ended unexpectedly - expected ')'. " + this.input); 750 | 751 | return result; 752 | } 753 | 754 | allArgs():string[] { 755 | let as:string[] = []; 756 | let a:string; 757 | while (a = this.nextArg()) 758 | as.push(a); 759 | return as; 760 | } 761 | 762 | get where(): number { 763 | return this.pos; 764 | } 765 | 766 | cleanupArg(s:string):string { 767 | s = s.replace(/^\s*|\s*$/g, ""); 768 | if (s == "") 769 | return null; 770 | return s; 771 | } 772 | } 773 | 774 | function isInitial(c:string):boolean { 775 | return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_'; 776 | } 777 | 778 | function isSubsequent(c:string):boolean { 779 | return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '_'; 780 | } 781 | 782 | ////////////////////////////////////////////////////////////////////////////////////////// 783 | // 784 | // Type checking 785 | 786 | const knownTypes = new SMap(); 787 | const knownIds = new SMap(); 788 | const userTypes:UserDefn[] = []; 789 | 790 | function buildTypeMap():void { 791 | knownTypes.put("int8", new PrimitiveDefn("int8", 1, 1)); 792 | knownTypes.put("uint8", new PrimitiveDefn("uint8", 1, 1)); 793 | knownTypes.put("int16", new PrimitiveDefn("int16", 2, 2)); 794 | knownTypes.put("uint16", new PrimitiveDefn("uint16", 2, 2)); 795 | knownTypes.put("int32", new PrimitiveDefn("int32", 4, 4)); 796 | knownTypes.put("uint32", new PrimitiveDefn("uint32", 4, 4)); 797 | 798 | knownTypes.put("atomic/int8", new AtomicDefn("atomic/int8", 1, 1)); 799 | knownTypes.put("atomic/uint8", new AtomicDefn("atomic/uint8", 1, 1)); 800 | knownTypes.put("atomic/int16", new AtomicDefn("atomic/int16", 2, 2)); 801 | knownTypes.put("atomic/uint16", new AtomicDefn("atomic/uint16", 2, 2)); 802 | knownTypes.put("atomic/int32", new AtomicDefn("atomic/int32", 4, 4)); 803 | knownTypes.put("atomic/uint32", new AtomicDefn("atomic/uint32", 4, 4)); 804 | 805 | knownTypes.put("synchronic/int8", new SynchronicDefn("synchronic/int8", 12, 4, 1)); 806 | knownTypes.put("synchronic/uint8", new SynchronicDefn("synchronic/uint8", 12, 4, 1)); 807 | knownTypes.put("synchronic/int16", new SynchronicDefn("synchronic/int16", 12, 4, 2)); 808 | knownTypes.put("synchronic/uint16", new SynchronicDefn("synchronic/uint16", 12, 4, 2)); 809 | knownTypes.put("synchronic/int32", new SynchronicDefn("synchronic/int32", 12, 4, 4)); 810 | knownTypes.put("synchronic/uint32", new SynchronicDefn("synchronic/uint32", 12, 4, 4)); 811 | 812 | knownTypes.put("float32", new PrimitiveDefn("float32", 4, 4)); 813 | knownTypes.put("float64", new PrimitiveDefn("float64", 8, 8)); 814 | 815 | knownTypes.put("int32x4", new SIMDDefn("int32x4", 16, 16, 4)); 816 | knownTypes.put("float32x4", new SIMDDefn("float32x4", 16, 16, 4)); 817 | knownTypes.put("float64x2", new SIMDDefn("float64x2", 16, 16, 8)); 818 | 819 | for ( let s of allSources ) { 820 | for ( let d of s.defs ) { 821 | if (knownTypes.test(d.name)) 822 | throw new ProgramError(d.file, d.line, "Duplicate type name: " + d.name); 823 | knownTypes.put(d.name, d); 824 | userTypes.push(d); 825 | } 826 | } 827 | } 828 | 829 | // Reference checking: 830 | // - For each class type, check inheritance, and add a property inheritRef that references the definition 831 | // - For each property, check that the referenced type exists, and add a property typeRef that references the definition 832 | // - For each property, check that atomic/synchronic is only used on appropriate types 833 | 834 | function resolveTypeRefs():void { 835 | for ( let d of userTypes ) { 836 | if (d.kind == DefnKind.Class) { 837 | let cls = d; 838 | if (cls.baseName != "") { 839 | let probe = knownTypes.get(cls.baseName); 840 | if (!probe) 841 | throw new ProgramError(cls.file, cls.line, "Missing base type: " + cls.baseName); 842 | if (probe.kind != DefnKind.Class) 843 | throw new ProgramError(cls.file, cls.line, "Base type is not class: " + cls.baseName); 844 | cls.baseTypeRef = probe; 845 | cls.baseTypeRef.subclasses.push(cls); 846 | } 847 | } 848 | for ( let p of d.props ) { 849 | if (!knownTypes.test(p.typeName)) 850 | throw new ProgramError(d.file, p.line, "Undefined type: " + p.typeName); 851 | let ty:Defn = null; 852 | if (p.qual != PropQual.None) { 853 | if (p.qual == PropQual.Atomic) 854 | ty = knownTypes.get("atomic/" + p.typeName); 855 | else 856 | ty = knownTypes.get("synchronic/" + p.typeName); 857 | if (!ty) 858 | throw new ProgramError(d.file, p.line, ": Not " + (p.qual == PropQual.Atomic ? "an atomic" : "a synchronic") + " type: " + p.typeName); 859 | } 860 | else 861 | ty = knownTypes.get(p.typeName); 862 | p.typeRef = ty; 863 | } 864 | } 865 | } 866 | 867 | function checkRecursion():void { 868 | for ( let d of userTypes ) { 869 | if (d.kind == DefnKind.Struct) 870 | checkRecursionForStruct( d); 871 | else if (d.kind == DefnKind.Class) 872 | checkRecursionForClass( d); 873 | } 874 | 875 | // For a struct type, check that it does not include itself. 876 | function checkRecursionForStruct(d:StructDefn):void { 877 | if (d.checked) 878 | return; 879 | d.live = true; 880 | for ( let p of d.props ) { 881 | if (p.isArray) 882 | continue; 883 | let probe = knownTypes.get(p.typeName); 884 | if (!probe || probe.kind != DefnKind.Struct) 885 | continue; 886 | let s = probe; 887 | if (s.live) 888 | throw new ProgramError(d.file, p.line, "Recursive type reference to struct " + p.typeName + " from " + d.name); 889 | p.typeRef = s; 890 | checkRecursionForStruct(s); 891 | } 892 | d.live = false; 893 | d.checked = true; 894 | } 895 | 896 | // For a class type, check that it does not inherit from itself. 897 | function checkRecursionForClass(d:ClassDefn):void { 898 | if (d.checked) 899 | return; 900 | d.live = true; 901 | if (d.baseTypeRef) { 902 | if (d.baseTypeRef.live) 903 | throw new ProgramError(d.file, d.line, "Recursive type reference to base class from " + d.name); 904 | checkRecursionForClass(d.baseTypeRef); 905 | } 906 | d.live = false; 907 | d.checked = true; 908 | } 909 | } 910 | 911 | // Ugh, this is wrong for init(), where we want each class to have its 912 | // own. Really there's no reason to outlaw same-named methods in base 913 | // or subclass, except it's confusing to allow it. 914 | 915 | function checkMethods():void { 916 | for ( let d of userTypes ) { 917 | if (d.kind != DefnKind.Class) 918 | continue; 919 | let cls = d; 920 | for ( let m of d.methods ) { 921 | for ( let b=cls.baseTypeRef ; b ; b=b.baseTypeRef ) { 922 | let bm = b.getMethod(m.name); 923 | if (!bm) 924 | continue; 925 | if (m.kind == MethodKind.NonVirtual && bm.kind == MethodKind.Virtual) 926 | throw new ProgramError(cls.file, m.line, 927 | "Non-virtual method " + m.name + " is defined virtual in a base class " + b.name + " (" + b.file + ":" + b.line + ")"); 928 | if (m.kind == MethodKind.Virtual && bm.kind != MethodKind.Virtual) 929 | throw new ProgramError(cls.file, m.line, 930 | "Virtual method " + m.name + " is defined non-virtual in a base class " + b.name + " (" + b.file + ":" + b.line + ")"); 931 | if (m.kind == MethodKind.Virtual) { 932 | // Issue #34: check arity of methods, requires parsing parameter lists etc. 933 | } 934 | } 935 | } 936 | } 937 | } 938 | 939 | function layoutTypes():void { 940 | for ( let d of userTypes ) { 941 | if (d.kind == DefnKind.Class) 942 | layoutClass( d); 943 | else 944 | layoutStruct( d); 945 | } 946 | } 947 | 948 | function layoutClass(d:ClassDefn):void { 949 | let map = new SMap(); 950 | let size = 4; 951 | let align = 4; 952 | if (d.baseName != "") { 953 | if (d.baseTypeRef.map == null) 954 | layoutClass(d.baseTypeRef); 955 | map = d.baseTypeRef.map.copy(); 956 | size = d.baseTypeRef.size; 957 | align = d.baseTypeRef.align; 958 | } 959 | layoutDefn(d, map, size, align); 960 | // layoutDefn updates d.map, d.size, d.align 961 | d.className = (d.baseTypeRef ? (d.baseTypeRef.className + ">") : "") + d.name; 962 | d.classId = computeClassId(d.className); 963 | let idAsString = String(d.classId); 964 | if (knownIds.test(idAsString)) 965 | throw new ProgramError(d.file, d.line, "Duplicate class ID for " + d.className + ": previous=" + knownIds.get(idAsString).className); 966 | knownIds.put(idAsString, d); 967 | } 968 | 969 | function layoutStruct(d:UserDefn):void { 970 | layoutDefn(d, new SMap(), 0, 0); 971 | } 972 | 973 | function layoutDefn(d:UserDefn, map:SMap, size:number, align:number):void { 974 | for ( let p of d.props ) { 975 | let k = p.typeRef.kind; 976 | if (p.isArray) 977 | k = DefnKind.Class; 978 | switch (k) { 979 | case DefnKind.Primitive: { 980 | let pt = p.typeRef; 981 | size = (size + pt.size - 1) & ~(pt.size - 1); 982 | align = Math.max(align, pt.align); 983 | map.put(p.name, new MapEntry(p.name, true, size, pt)); 984 | size += pt.size; 985 | break; 986 | } 987 | case DefnKind.Class: { 988 | // Could also be array, don't look at the contents 989 | size = (size + (Defn.pointerAlign - 1)) & ~(Defn.pointerAlign - 1); 990 | align = Math.max(align, Defn.pointerAlign); 991 | map.put(p.name, new MapEntry(p.name, true, size, knownTypes.get(Defn.pointerTypeName))); 992 | size += Defn.pointerSize; 993 | break; 994 | } 995 | case DefnKind.Struct: { 996 | let st = p.typeRef; 997 | if (st.map == null) 998 | layoutStruct(st); 999 | size = (size + st.align - 1) & ~(st.align - 1); 1000 | align = Math.max(align, st.align); 1001 | map.put(p.name, new MapEntry(p.name, false, size, st)); 1002 | let root = p.name; 1003 | let mIter = st.map.values(); 1004 | for ( let fld=mIter.next() ; fld ; fld=mIter.next() ) { 1005 | let fldname = root + "." + fld.name; 1006 | map.put(fldname, new MapEntry(fldname, fld.expand, size + fld.offset, fld.type)); 1007 | } 1008 | size += st.size; 1009 | break; 1010 | } 1011 | } 1012 | } 1013 | // Struct size must be rounded up to alignment so that n*SIZE makes a valid array: 1014 | // each array element must be suitably aligned. 1015 | if (d.kind == DefnKind.Struct) 1016 | size = (size + align - 1) & ~(align - 1); 1017 | d.map = map; 1018 | d.size = size; 1019 | d.align = align; 1020 | } 1021 | 1022 | // Compute a 28-bit nonnegative hash value for the name. This needs 1023 | // to be *globally* unique (ie across workers). There's really no 1024 | // easy way to guarantee that, but we check uniqueness within each 1025 | // program and so long as a type used in both programs is unique 1026 | // within both programs we're mostly fine. The risk is that type A in 1027 | // program P1 is misidentified as type B in program P2 because A and B 1028 | // have the same class ID and P1 does not include B and P2 does not 1029 | // include A. But that's technically a bug in the programs. 1030 | 1031 | function computeClassId(name:string):number { 1032 | let n = name.length; 1033 | for (let i=0 ; i < name.length ; i++ ) { 1034 | let c = name.charAt(i); 1035 | let v = 0; 1036 | if (c >= 'A' && c <= 'Z') 1037 | v = c.charCodeAt(0) - 'A'.charCodeAt(0); 1038 | else if (c >= 'a' && c <= 'z') 1039 | v = c.charCodeAt(0) - 'a'.charCodeAt(0) + 26; 1040 | else if (c >= '0' && c <= '9') 1041 | v = c.charCodeAt(0) - '0'.charCodeAt(0) + 52; 1042 | else if (c == '_') 1043 | v = 62; 1044 | else if (c == '>') 1045 | v = 63; 1046 | else 1047 | throw new InternalError("Bad character in class name: " + c); 1048 | n = (((n & 0x1FFFFFF) << 3) | (n >>> 25)) ^ v; 1049 | } 1050 | return n; 1051 | } 1052 | 1053 | // For each class, create a representation of its vtable 1054 | 1055 | function createVirtuals():void { 1056 | for ( let t of userTypes ) 1057 | if (t.kind == DefnKind.Class) 1058 | createVirtualsFor( t); 1059 | } 1060 | 1061 | /* 1062 | for ( a given virtual name in me or my parent ) 1063 | for ( my subclass ids including me ) 1064 | create a case that tests that id: 1065 | dispatch to the first impl in the chain starting at that subclass, if any, stopping at me 1066 | create a default 1067 | if the virtual is inherited by me then dispatch to the first impl in the chain starting at my parent 1068 | otherwise throw 1069 | */ 1070 | 1071 | function createVirtualsFor(cls: ClassDefn): void { 1072 | let vtable:Virtual[] = []; 1073 | let virts = new VirtualMethodIterator(cls); 1074 | for ( let [mname, sign, isInherited] = virts.next() ; mname != "" ; [mname, sign, isInherited] = virts.next() ) { 1075 | let reverseCases = new SMap(); 1076 | let subs = new InclusiveSubclassIterator(cls); 1077 | for ( let subcls = subs.next() ; subcls ; subcls = subs.next() ) { 1078 | let impl = findMethodImplFor(subcls, cls.baseTypeRef, mname); 1079 | if (!impl) 1080 | continue; 1081 | if (!reverseCases.test(impl)) 1082 | reverseCases.put(impl, []); 1083 | reverseCases.get(impl).push(subcls.classId); 1084 | } 1085 | let def:string = null; 1086 | if (isInherited && cls.baseTypeRef) 1087 | def = findMethodImplFor(cls.baseTypeRef, null, mname); 1088 | vtable.push(new Virtual(mname, sign, reverseCases, def)); 1089 | } 1090 | cls.vtable = vtable; 1091 | } 1092 | 1093 | function findMethodImplFor(cls:ClassDefn, stopAt:ClassDefn, name:string):string { 1094 | if (cls == stopAt) 1095 | return null; 1096 | if (cls.hasMethod(name)) 1097 | return cls.name + "." + name + "_impl"; 1098 | if (cls.baseTypeRef) 1099 | return findMethodImplFor(cls.baseTypeRef, stopAt, name); 1100 | throw new InternalError("Method not found: " + name); 1101 | } 1102 | 1103 | function findType(name:string):Defn { 1104 | if (!knownTypes.test(name)) 1105 | throw new InternalError("Unknown type in sizeofType: " + name); 1106 | return knownTypes.get(name); 1107 | } 1108 | 1109 | ////////////////////////////////////////////////////////////////////////////////////////// 1110 | // 1111 | // Macro expansion and pasteup 1112 | 1113 | const self_getter1_re = new RegExp("SELF" + Path + NullaryOperation, "g"); 1114 | const self_getter2_re = new RegExp("SELF" + Path, "g"); 1115 | const self_accessor_re = new RegExp("SELF" + Path + OperationLParen, "g"); 1116 | const self_setter_re = new RegExp("SELF" + Path + Os + AssignOp + Os, "g"); 1117 | const self_invoke_re = new RegExp("SELF\\.(" + Id + ")" + LParen, "g"); 1118 | 1119 | // Name validity will be checked on the next expansion pass. 1120 | 1121 | function expandSelfAccessors():void { 1122 | for ( var t of userTypes ) { // ES6 required for 'let' here 1123 | for ( let m of t.methods ) { 1124 | let body = m.body; 1125 | for ( let k=0 ; k < body.length ; k++ ) { 1126 | body[k] = myExec(t.file, t.line, self_setter_re, 1127 | function (file:string, line:number, s:string, p:number, m:RegExpExecArray):[string,number] { 1128 | if (p > 0 && isSubsequent(s.charAt(p-1))) return [s, p+m.length]; 1129 | return replaceSetterShorthand(file, line, s, p, m, t); 1130 | }, 1131 | body[k]); 1132 | body[k] = body[k].replace(self_accessor_re, function (m, path, operation, p, s) { 1133 | if (p > 0 && isSubsequent(s.charAt(p-1))) return m; 1134 | return t.name + path + "." + operation + "(SELF, "; 1135 | }); 1136 | body[k] = body[k].replace(self_invoke_re, function (m, id, p, s) { 1137 | if (p > 0 && isSubsequent(s.charAt(p-1))) return m; 1138 | var pp = new ParamParser(t.file, t.line, s, p+m.length); 1139 | var args = pp.allArgs(); 1140 | return t.name + "." + id + "(SELF" + (args.length > 0 ? ", " : " "); 1141 | }); 1142 | body[k] = body[k].replace(self_getter1_re, function (m, path, operation, p, s) { 1143 | if (p > 0 && isSubsequent(s.charAt(p-1))) return m; 1144 | return t.name + path + "." + operation + "(SELF)"; 1145 | }); 1146 | body[k] = body[k].replace(self_getter2_re, function (m, path, p, s) { 1147 | if (p > 0 && isSubsequent(s.charAt(p-1))) return m; 1148 | return t.name + path + "(SELF)"; 1149 | }); 1150 | } 1151 | } 1152 | } 1153 | } 1154 | 1155 | // We've eaten "SELF.id op " and need to grab a plausible RHS. 1156 | // 1157 | // Various complications here: 1158 | // 1159 | // nested fields: SELF.x_y_z += 10 1160 | // stacked: SELF.x = SELF.y = SELF.z = 0 1161 | // used for value: v = (SELF.x = 10) 1162 | // 1163 | // Easiest fix is to change the spec so that a setter returns a value, 1164 | // which is the rhs. The regular assignment and Atomics.store 1165 | // already does that. I just changed _synchronicsStore so that it 1166 | // does that too. BUT SIMD STORE INSTRUCTIONS DO NOT. Good grief. 1167 | // 1168 | // For now, disallow stacking of simd values (but don't detect it). 1169 | 1170 | const AssignmentOps = 1171 | { "=": "set", 1172 | "+=": "add", 1173 | "-=": "sub", 1174 | "&=": "and", 1175 | "|=": "or", 1176 | "^=": "xor" 1177 | }; 1178 | 1179 | function replaceSetterShorthand(file:string, line:number, s:string, p:number, ms:RegExpExecArray, t:UserDefn):[string,number] { 1180 | //return [s, p+m.length]; 1181 | let m = ms[0]; 1182 | let path = ms[1]; 1183 | let operation = ms[2]; 1184 | let left = s.substring(0,p); 1185 | let pp = new ParamParser(file, line, s, p+m.length, false, true); 1186 | let rhs = pp.nextArg(); 1187 | if (!rhs) 1188 | throw new ProgramError(file, line, "Missing right-hand-side expression in assignment"); 1189 | // Be sure to re-expand the RHS. 1190 | let substitution_left = `${left} ${t.name}${path}.${AssignmentOps[operation]}(SELF, `; 1191 | return [`${substitution_left} ${rhs})${pp.sawSemi ? ';' : ''} ${s.substring(pp.where)}`, 1192 | substitution_left.length]; 1193 | } 1194 | 1195 | function linePusher(info:() => [string,number], nlines:SourceLine[]): (text:string) => void { 1196 | return function (text:string):void { 1197 | let [file,line] = info(); 1198 | nlines.push(new SourceLine(file, line, text)); 1199 | } 1200 | } 1201 | 1202 | function pasteupTypes():void { 1203 | var emitFn = ""; // ES5 workaround - would otherwise be local to inner "for" loop 1204 | var emitLine = 0; // ditto 1205 | for ( let source of allSources ) { 1206 | let defs = source.defs; 1207 | let lines = source.lines; 1208 | let nlines: SourceLine[] = []; 1209 | let k = 0; 1210 | for ( let d of defs ) { 1211 | while (k < d.origin && k < lines.length) 1212 | nlines.push(lines[k++]); 1213 | 1214 | let push = linePusher(function ():[string,number] { return [emitFn, emitLine++] }, nlines); 1215 | 1216 | emitFn = d.file + "[class definition]"; 1217 | emitLine = d.line; 1218 | if (d.kind == DefnKind.Class) 1219 | push("function " + d.name + "(p) { this._pointer = (p|0); }"); 1220 | else 1221 | push("function " + d.name + "() {}"); 1222 | if (d.kind == DefnKind.Class) { 1223 | let cls = d; 1224 | if (cls.baseName) 1225 | push(d.name + ".prototype = new " + cls.baseName + ";"); 1226 | else 1227 | push("Object.defineProperty(" + d.name + ".prototype, 'pointer', { get: function () { return this._pointer } });"); 1228 | } 1229 | push(d.name + ".NAME = \"" + d.name + "\";"); 1230 | push(d.name + ".SIZE = " + d.size + ";"); 1231 | push(d.name + ".ALIGN = " + d.align + ";"); 1232 | if (d.kind == DefnKind.Class) { 1233 | let cls = d; 1234 | push(d.name + ".CLSID = " + cls.classId + ";"); 1235 | push("Object.defineProperty(" + d.name + ", 'BASE', {get: function () { return " + (cls.baseName ? cls.baseName : "null") + "; }});"); 1236 | } 1237 | 1238 | // Now do methods. 1239 | // 1240 | // Implementation methods are emitted directly in the defining type, with a name suffix _impl. 1241 | // For struct methods, the name is "_get_impl", "_set_impl", or "_copy_impl". 1242 | 1243 | let haveSetter = false; 1244 | let haveGetter = false; 1245 | for ( let m of d.methods ) { 1246 | let name = m.name; 1247 | if (name == "") { 1248 | switch (m.kind) { 1249 | case MethodKind.Get: 1250 | if (haveGetter) 1251 | throw new ProgramError(d.file, m.line, "Duplicate struct getter"); 1252 | name = "_get_impl"; 1253 | haveGetter = true; 1254 | break; 1255 | case MethodKind.Set: 1256 | if (haveSetter) 1257 | throw new ProgramError(d.file, m.line, "Duplicate struct setter"); 1258 | name = "_set_impl"; 1259 | haveSetter = true; 1260 | break; 1261 | } 1262 | } 1263 | else if (m.kind == MethodKind.NonVirtual) 1264 | ; 1265 | else 1266 | name += "_impl"; 1267 | emitFn = d.file + "[method " + name + "]"; 1268 | emitLine = m.line; 1269 | let body = m.body; 1270 | // Formatting: useful to strip all trailing blank lines from 1271 | // the body first. 1272 | let last = body.length-1; 1273 | while (last > 0 && /^\s*$/.test(body[last])) 1274 | last--; 1275 | if (last == 0) 1276 | push(d.name + "." + name + " = function " + body[0]); 1277 | else { 1278 | push(d.name + "." + name + " = function " + body[0]); 1279 | for ( let x=1; x < last ; x++ ) 1280 | push(body[x]); 1281 | push(body[last]); 1282 | } 1283 | } 1284 | 1285 | // Now default methods, if appropriate. 1286 | 1287 | if (d.kind == DefnKind.Struct) { 1288 | var struct = d; 1289 | if (!haveGetter) { 1290 | push(d.name + "._get_impl = function (SELF) {"); 1291 | push(" var v = new " + d.name + ";"); 1292 | // Use longhand for access, since self accessors are expanded before pasteup. 1293 | // TODO: Would be useful to fix that. 1294 | for ( var p of d.props ) 1295 | push(" v." + p.name + " = " + d.name + "." + p.name + "(SELF);"); 1296 | push(" return v;"); 1297 | push("}"); 1298 | struct.hasGetMethod = true; 1299 | } 1300 | 1301 | if (!haveSetter) { 1302 | push(d.name + "._set_impl = function (SELF, v) {"); 1303 | // TODO: as above. 1304 | for ( var p of d.props ) 1305 | push(" " + d.name + "." + p.name + ".set(SELF, v." + p.name + ");"); 1306 | push("}"); 1307 | struct.hasSetMethod = true; 1308 | } 1309 | } 1310 | 1311 | // Now do vtable, if appropriate. 1312 | 1313 | if (d.kind == DefnKind.Class) { 1314 | let cls = d; 1315 | for ( let virtual of cls.vtable ) { 1316 | // Shouldn't matter much 1317 | emitFn = d.file + "[vtable " + virtual.name + "]"; 1318 | emitLine = d.line; 1319 | let signature = virtual.signature(); 1320 | push(d.name + "." + virtual.name + " = function (SELF " + signature + ") {"); 1321 | push(" switch (_mem_int32[SELF>>2]) {"); 1322 | let kv = virtual.reverseCases.keysValues(); 1323 | for ( let [name,cases]=kv.next() ; name ; [name,cases]=kv.next() ) { 1324 | for ( let c of cases ) 1325 | push(` case ${c}:`); 1326 | push(` return ${name}(SELF ${signature});`); 1327 | } 1328 | push(" default:"); 1329 | push(" " + (virtual.default_ ? 1330 | `return ${virtual.default_}(SELF ${signature})` : 1331 | "throw FlatJS._badType(SELF)") + ";"); 1332 | push(" }"); 1333 | push("}"); 1334 | } 1335 | } 1336 | 1337 | // Now do other methods: initInstance. 1338 | 1339 | if (d.kind == DefnKind.Class) { 1340 | let cls = d; 1341 | push(d.name + ".initInstance = function(SELF) { _mem_int32[SELF>>2]=" + cls.classId + "; return SELF; }"); 1342 | } 1343 | 1344 | if (d.kind == DefnKind.Class) 1345 | push("FlatJS._idToType[" + ( d).classId + "] = " + d.name + ";"); 1346 | } 1347 | while (k < lines.length) 1348 | nlines.push(lines[k++]); 1349 | source.lines = nlines; 1350 | } 1351 | } 1352 | 1353 | function expandGlobalAccessorsAndMacros():void { 1354 | for ( let source of allSources ) { 1355 | let lines = source.lines; 1356 | let nlines: SourceLine[] = []; 1357 | for ( let l of lines ) 1358 | nlines.push(new SourceLine(l.file, l.line, expandMacrosIn(l.file, l.line, l.text))); 1359 | source.lines = nlines; 1360 | } 1361 | } 1362 | 1363 | // TODO: it's likely that the expandMacrosIn is really better 1364 | // represented as a class, with a ton of methods and locals (eg for 1365 | // file and line), performing expansion on one line. 1366 | 1367 | const new_re = new RegExp("@new\\s+(" + Id + ")" + QualifierOpt + "(?:\\.(Array)" + LParen + ")?", "g"); 1368 | 1369 | const acc_re = new RegExp("(" + Id + ")" + PathOptLazy + "(?:" + Operation + "|)" + LParen, "g"); 1370 | 1371 | // It would sure be nice to avoid the explicit ".Array" here, but I don't yet know how. 1372 | const arr_re = new RegExp("(" + Id + ")" + QualifierOpt + "\\.Array" + PathOpt + Operation + LParen, "g"); 1373 | 1374 | function expandMacrosIn(file:string, line:number, text:string):string { 1375 | return myExec(file, line, new_re, newMacro, 1376 | myExec(file, line, arr_re, arrMacro, 1377 | myExec(file, line, acc_re, accMacro, text))); 1378 | } 1379 | 1380 | function myExec(file:string, line:number, re:RegExp, macro:(fn:string, l:number, s:string, p:number, m:RegExpExecArray)=>[string,number], text:string):string { 1381 | let old = re.lastIndex; 1382 | re.lastIndex = 0; 1383 | 1384 | for (;;) { 1385 | let m = re.exec(text); 1386 | if (!m) 1387 | break; 1388 | // The trick here is that we may replace more than the match: 1389 | // the macro may eat additional input. So the macro should 1390 | // be returning a new string, as well as the index at which 1391 | // to continue the search. 1392 | let [newText, newStart] = macro(file, line, text, re.lastIndex-m[0].length, m); 1393 | text = newText; 1394 | re.lastIndex = newStart; 1395 | } 1396 | 1397 | re.lastIndex = old; 1398 | return text; 1399 | } 1400 | 1401 | // Here, arity includes the self argument 1402 | 1403 | const OpAttr = { 1404 | "get": { arity: 1, atomic: "load", synchronic: "" }, 1405 | "ref": { arity: 1, atomic: "", synchronic: "" }, 1406 | "notify": { arity: 1, atomic: "", synchronic: "_synchronicNotify" }, 1407 | "set": { arity: 2, atomic: "store", synchronic: "_synchronicStore", vanilla: "=" }, 1408 | "add": { arity: 2, atomic: "add", synchronic: "_synchronicAdd", vanilla: "+=" }, 1409 | "sub": { arity: 2, atomic: "sub", synchronic: "_synchronicSub", vanilla: "-=" }, 1410 | "and": { arity: 2, atomic: "and", synchronic: "_synchronicAnd", vanilla: "&=" }, 1411 | "or": { arity: 2, atomic: "or", synchronic: "_synchronicOr", vanilla: "|=" }, 1412 | "xor": { arity: 2, atomic: "xor", synchronic: "_synchronicXor", vanilla: "^=" }, 1413 | "loadWhenEqual": { arity: 2, atomic: "", synchronic: "_synchronicLoadWhenEqual" }, 1414 | "loadWhenNotEqual": { arity: 2, atomic: "", synchronic: "_synchronicLoadWhenNotEqual" }, 1415 | "expectUpdate": { arity: 3, atomic: "", synchronic: "_synchronicExpectUpdate" }, 1416 | "compareExchange": { arity: 3, atomic: "compareExchange", synchronic: "_synchronicCompareExchange" }, 1417 | }; 1418 | 1419 | // "at": { arity: 1, atomic: "", synchronic: "" } 1420 | 1421 | function accMacro(file:string, line:number, s:string, p:number, ms:RegExpExecArray):[string,number] { 1422 | let m = ms[0]; 1423 | let className = ms[1]; 1424 | let propName = ""; 1425 | let operation = ""; 1426 | 1427 | let nomatch:[string,number] = [s, p+m.length]; 1428 | let left = s.substring(0,p); 1429 | 1430 | if (!ms[2] && !ms[3]) 1431 | return nomatch; // We're looking at something else 1432 | 1433 | propName = ms[2] ? ms[2].substring(1) : ""; // Strip the leading "." 1434 | operation = ms[3] ? ms[3] : "get"; 1435 | 1436 | let ty = knownTypes.get(className); 1437 | if (!ty) 1438 | return nomatch; 1439 | 1440 | let offset = 0; 1441 | let targetType: Defn = null; 1442 | 1443 | if (propName == "") { 1444 | if (!(ty.kind == DefnKind.Primitive || ty.kind == DefnKind.Struct)) 1445 | throw new ProgramError(file, line, "Operation '" + operation + "' without a path requires a value type: " + s); 1446 | offset = 0; 1447 | targetType = ty; 1448 | } 1449 | else { 1450 | if (!(ty.kind == DefnKind.Class || ty.kind == DefnKind.Struct)) { 1451 | //throw new ProgramError(file, line, "Operation with a path requires a structured type: " + s); 1452 | return nomatch; 1453 | } 1454 | 1455 | let cls = ty; 1456 | // findAccessibleFieldFor will vet the operation against the field type, 1457 | // so atomic/synchronic ops will only be allowed on appropriate types 1458 | 1459 | let fld = cls.findAccessibleFieldFor(operation, propName); 1460 | if (!fld) { 1461 | let fld2 = cls.findAccessibleFieldFor("get", propName); 1462 | if (fld2) 1463 | warning(file, line, "No match for " + className + " " + operation + " " + propName); 1464 | return nomatch; 1465 | } 1466 | offset = fld.offset; 1467 | targetType = fld.type; 1468 | } 1469 | 1470 | let pp = new ParamParser(file, line, s, p+m.length); 1471 | let as = (pp).allArgs(); 1472 | if (OpAttr[operation].arity != as.length) { 1473 | warning(file, line, `Bad accessor arity ${propName} / ${as.length}: ` + s); 1474 | return nomatch; 1475 | }; 1476 | 1477 | // Issue #16: Watch it: Parens interact with semicolon insertion. 1478 | let ref = `(${expandMacrosIn(file, line, endstrip(as[0]))} + ${offset})`; 1479 | if (operation == "ref") { 1480 | return [left + ref + s.substring(pp.where), 1481 | left.length + ref.length]; 1482 | } 1483 | 1484 | return loadFromRef(file, line, ref, targetType, s, left, operation, pp, as[1], as[2], nomatch); 1485 | } 1486 | 1487 | function loadFromRef(file:string, line:number, 1488 | ref:string, type:Defn, s:string, left:string, operation:string, pp:ParamParser, 1489 | rhs:string, rhs2:string, nomatch:[string,number]):[string,number] 1490 | { 1491 | let mem="", size=0, synchronic=false, atomic=false, simd=false, shift=-1, simdType=""; 1492 | if (type.kind == DefnKind.Primitive) { 1493 | let prim = type; 1494 | mem = prim.memory; 1495 | synchronic = prim.primKind == PrimKind.Synchronic; 1496 | atomic = prim.primKind == PrimKind.Atomic; 1497 | simd = prim.primKind == PrimKind.SIMD; 1498 | if (synchronic) 1499 | shift = log2(( prim).baseSize); 1500 | else if (simd) 1501 | shift = log2(( prim).baseSize); 1502 | else 1503 | shift = log2(prim.size); 1504 | if (simd) 1505 | simdType = prim.name; 1506 | } 1507 | else if (type.kind == DefnKind.Class) { 1508 | mem = Defn.pointerMemName; 1509 | shift = log2(Defn.pointerSize); 1510 | } 1511 | if (shift >= 0) { 1512 | let expr = ""; 1513 | let op = ""; 1514 | switch (OpAttr[operation].arity) { 1515 | case 1: 1516 | break; 1517 | case 2: 1518 | rhs = expandMacrosIn(file, line, endstrip(rhs)); 1519 | break; 1520 | case 3: 1521 | rhs = expandMacrosIn(file, line, endstrip(rhs)); 1522 | rhs2 = expandMacrosIn(file, line, endstrip(rhs2)); 1523 | break; 1524 | default: 1525 | throw new InternalError("No operator: " + operation + " " + s); 1526 | } 1527 | let fieldIndex = ""; 1528 | if (synchronic) 1529 | fieldIndex = `(${ref} + ${SynchronicDefn.bias}) >> ${shift}`; 1530 | else 1531 | fieldIndex = `${ref} >> ${shift}`; 1532 | switch (operation) { 1533 | case "get": 1534 | if (atomic || synchronic) 1535 | expr = `Atomics.load(${mem}, ${fieldIndex})`; 1536 | else if (simd) 1537 | expr = `SIMD.${simdType}.load(${mem}, ${fieldIndex})`; 1538 | else 1539 | expr = `${mem}[${fieldIndex}]`; 1540 | break; 1541 | case "notify": 1542 | expr = `FlatJS.${OpAttr[operation].synchronic}(${ref})`; 1543 | break; 1544 | case "set": 1545 | case "add": 1546 | case "sub": 1547 | case "and": 1548 | case "or": 1549 | case "xor": 1550 | case "loadWhenEqual": 1551 | case "loadWhenNotEqual": 1552 | if (atomic) 1553 | expr = `Atomics.${OpAttr[operation].atomic}(${mem}, ${fieldIndex}, ${rhs})`; 1554 | else if (synchronic) 1555 | expr = `FlatJS.${OpAttr[operation].synchronic}(${ref}, ${mem}, ${fieldIndex}, ${rhs})`; 1556 | else if (simd) 1557 | expr = `SIMD.${simdType}.store(${mem}, ${fieldIndex}, ${rhs})`; 1558 | else 1559 | expr = `${mem}[${ref} >> ${shift}] ${OpAttr[operation].vanilla} ${rhs}`; 1560 | break; 1561 | case "compareExchange": 1562 | case "expectUpdate": 1563 | if (atomic) 1564 | expr = `Atomics.${OpAttr[operation].atomic}(${mem}, ${fieldIndex}, ${rhs}, ${rhs2})`; 1565 | else 1566 | expr = `FlatJS.${OpAttr[operation].synchronic}(${ref}, ${mem}, ${fieldIndex}, ${rhs}, ${rhs2})`; 1567 | break; 1568 | default: 1569 | throw new InternalError("No operator: " + operation + " line: " + s); 1570 | } 1571 | // Issue #16: Parens interact with semicolon insertion. 1572 | //expr = `(${expr})`; 1573 | return [left + expr + s.substring(pp.where), left.length + expr.length]; 1574 | } 1575 | else { 1576 | let t = type; 1577 | let expr = ""; 1578 | // Field type is a structure. If the structure type has a getter then getting is allowed 1579 | // and should be rewritten as a call to the getter, passing the field reference. 1580 | // Ditto setter, which will also pass secondArg. 1581 | switch (operation) { 1582 | case "get": 1583 | if (t.hasGetMethod) 1584 | expr = `${t.name}._get_impl(${ref})`; 1585 | break; 1586 | case "set": 1587 | if (t.hasSetMethod) 1588 | expr = `${t.name}._set_impl(${ref}, ${expandMacrosIn(file, line, endstrip(rhs))})`; 1589 | break; 1590 | case "ref": 1591 | expr = ref; 1592 | break; 1593 | } 1594 | if (expr == "") { 1595 | warning(file, line, "No operation " + operation + " allowed"); 1596 | return nomatch; 1597 | } 1598 | // Issue #16: Parens interact with semicolon insertion. 1599 | //expr = `(${expr})`; 1600 | return [left + expr + s.substring(pp.where), left.length + expr.length]; 1601 | } 1602 | } 1603 | 1604 | function arrMacro(file:string, line:number, s:string, p:number, ms:RegExpExecArray):[string,number] { 1605 | let m=ms[0]; 1606 | let typeName=ms[1]; 1607 | let qualifier=ms[2]; 1608 | let field=ms[3] ? ms[3].substring(1) : ""; 1609 | let operation=ms[4]; 1610 | let nomatch:[string,number] = [s,p+m.length]; 1611 | 1612 | if (operation == "get" || operation == "set") 1613 | throw new ProgramError(file, line, "Use 'at' and 'setAt' on Arrays"); 1614 | if (operation == "at") 1615 | operation = "get"; 1616 | if (operation == "setAt") 1617 | operation = "set"; 1618 | 1619 | let type = findType(typeName); 1620 | if (!type) 1621 | return nomatch; 1622 | 1623 | let pp = new ParamParser(file, line, s, p+m.length); 1624 | let as = (pp).allArgs(); 1625 | 1626 | if (as.length != OpAttr[operation].arity+1) { 1627 | warning(file, line, `Wrong arity for accessor ${operation} / ${as.length}`); 1628 | return nomatch; 1629 | }; 1630 | 1631 | let multiplier = type.elementSize; 1632 | if (type.kind == DefnKind.Primitive) { 1633 | if (field) 1634 | return nomatch; 1635 | } 1636 | else if (type.kind == DefnKind.Class) { 1637 | if (field) 1638 | return nomatch; 1639 | } 1640 | let ref = "(" + expandMacrosIn(file, line, endstrip(as[0])) + "+" + multiplier + "*" + expandMacrosIn(file, line, endstrip(as[1])) + ")"; 1641 | if (field) { 1642 | let fld = ( type).findAccessibleFieldFor(operation, field); 1643 | if (!fld) 1644 | return nomatch; 1645 | // Issue #16: Watch it: Parens interact with semicolon insertion. 1646 | ref = "(" + ref + "+" + fld.offset + ")"; 1647 | type = fld.type; 1648 | } 1649 | if (operation == "ref") { 1650 | let left = s.substring(0,p); 1651 | return [left + ref + s.substring(pp.where), 1652 | left.length + ref.length]; 1653 | } 1654 | 1655 | return loadFromRef(file, line, ref, type, s, s.substring(0,p), operation, pp, as[2], as[3], nomatch); 1656 | } 1657 | 1658 | // Since @new is new syntax, we throw errors for all misuse. 1659 | 1660 | function newMacro(file:string, line:number, s:string, p:number, ms:RegExpExecArray):[string,number] { 1661 | let m=ms[0]; 1662 | let baseType=ms[1]; 1663 | let qualifier=ms[2]; 1664 | let isArray=ms[3] == "Array"; 1665 | let left = s.substring(0,p); 1666 | 1667 | // Issue #27 - implement this. 1668 | if (qualifier) 1669 | throw new InternalError("Qualifiers on array @new not yet implemented"); 1670 | 1671 | let t = knownTypes.get(baseType); 1672 | if (!t) 1673 | throw new ProgramError(file, line, "Unknown type argument to @new: " + baseType); 1674 | 1675 | if (!isArray) { 1676 | let expr = "FlatJS.allocOrThrow(" + t.size + "," + t.align + ")"; 1677 | if (t.kind == DefnKind.Class) { 1678 | // NOTE, parens removed here 1679 | // Issue #16: Watch it: Parens interact with semicolon insertion. 1680 | expr = baseType + ".initInstance(" + expr + ")"; 1681 | } 1682 | return [left + expr + s.substring(p + m.length), 1683 | left.length + expr.length ]; 1684 | } 1685 | 1686 | let pp = new ParamParser(file, line, s, p+m.length); 1687 | let as = pp.allArgs(); 1688 | if (as.length != 1) 1689 | throw new ProgramError(file, line, "Wrong number of arguments to @new " + baseType + ".Array"); 1690 | 1691 | // NOTE, parens removed here 1692 | // Issue #16: Watch it: Parens interact with semicolon insertion. 1693 | let expr = "FlatJS.allocOrThrow(" + t.elementSize + " * " + expandMacrosIn(file, line, endstrip(as[0])) + ", " + t.elementAlign + ")"; 1694 | return [left + expr + s.substring(pp.where), 1695 | left.length + expr.length]; 1696 | } 1697 | 1698 | // This can also check if x is already properly parenthesized, though that 1699 | // involves counting parens, at least trivially (and then does it matter?). 1700 | // Consider (a).(b), which should be parenthesized as ((a).(b)). 1701 | // 1702 | // Issue #16: Parentheses are not actually reliable. 1703 | 1704 | function endstrip(x:string):string { 1705 | if (/^[a-zA-Z0-9]+$/.test(x)) 1706 | return x; 1707 | return "(" + x + ")"; 1708 | } 1709 | 1710 | main(process.argv.slice(2)); 1711 | -------------------------------------------------------------------------------- /libflatjs.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * Author: Lars T Hansen, lhansen@mozilla.com 6 | */ 7 | 8 | /* libflatjs.js - load this before loading your compiled FlatJS program. 9 | * 10 | * Call FlatJS.init before using, see documentation below. 11 | * 12 | * NOTE: The following needs to be valid "ES5+" code - close enough 13 | * to ES5 to run in all major browsers. 14 | */ 15 | 16 | /* 17 | * If no Atomics, then polyfill with versions that throw. These will 18 | * ensure that atomics and synchronics throw if the memory is not 19 | * shared. 20 | */ 21 | if (typeof "Atomics" == "undefined") { 22 | Atomics = { load: function () { throw "No Atomics"; }, 23 | store: function () { throw "No Atomics"; }, 24 | add: function () { throw "No Atomics"; }, 25 | sub: function () { throw "No Atomics"; }, 26 | and: function () { throw "No Atomics"; }, 27 | or: function () { throw "No Atomics"; }, 28 | xor: function () { throw "No Atomics"; }, 29 | compareExchange: function () { throw "No Atomics"; } 30 | }; 31 | } 32 | 33 | var _mem_int8 = null; 34 | var _mem_uint8 = null; 35 | var _mem_int16 = null; 36 | var _mem_uint16 = null; 37 | var _mem_int32 = null; 38 | var _mem_uint32 = null; 39 | var _mem_float32 = null; 40 | var _mem_float64 = null; 41 | 42 | var FlatJS = 43 | { 44 | /* 45 | * Initialize the local FlatJS instance. 46 | * 47 | * "buffer" can be an ArrayBuffer or SharedArrayBuffer. In the 48 | * latter case, all workers must pass the same buffer during 49 | * initialization. 50 | * 51 | * The buffer must be zero-initialized before being passed to 52 | * init(). FlatJS assumes ownership of the buffer, client code 53 | * should not access it directly after using it to initialize 54 | * the heap. 55 | * 56 | * "start" must be a valid offset within the buffer, it is the 57 | * first byte that may be used. 58 | * 59 | * "limit" must be a valid offset within the buffer, limit-1 is 60 | * the last byte that may be used. 61 | * 62 | * "initialize" must be true in exactly one agent and that call 63 | * must return before any agent can call any other methods on 64 | * their local FlatJS objects. Normally, you would allocate your 65 | * memory in the main thread, call FlatJS.init(buffer, true) in 66 | * the main thread, and then distribute the buffer to workers. 67 | */ 68 | init: function (buffer, start, limit, initialize) { 69 | if (arguments.length < 3) 70 | throw new Error("Required arguments: buffer, start, limit"); 71 | if (buffer instanceof ArrayBuffer) 72 | _FlatJS_init_ab(this, buffer, start, limit, initialize); 73 | else if (buffer instanceof SharedArrayBuffer) 74 | _FlatJS_init_sab(this, buffer, start, limit, initialize); 75 | else 76 | throw new Error("FlatJS can be initialized only on SharedArrayBuffer or ArrayBuffer"); 77 | }, 78 | 79 | /* 80 | * Given a nonnegative size in bytes and a nonnegative 81 | * power-of-two alignment, allocate and zero-initialize an object 82 | * of the necessary size (or larger) and required alignment, and 83 | * return its address. 84 | * 85 | * Return NULL if no memory is available. 86 | */ 87 | alloc: function (nbytes, alignment) { 88 | // Overridden during initialization. 89 | throw new Error("Not initialized" ); 90 | }, 91 | 92 | /* 93 | * Ditto, but throw if no memory is available. 94 | * 95 | * Interesting possibility is to avoid this function 96 | * and instead move the test into each initInstance(). 97 | */ 98 | allocOrThrow: function (nbytes, alignment) { 99 | var p = this.alloc(nbytes, alignment); 100 | if (p == 0) 101 | throw new MemoryError("Out of memory"); 102 | return p; 103 | }, 104 | 105 | /* 106 | * Given a pointer returned from alloc or calloc, free the memory. 107 | * p may be NULL in which case the call does nothing. 108 | */ 109 | free: function (p) { 110 | // Drop it on the floor, for now 111 | // In the future: figure out the size from the header or other info, 112 | // add to free list, etc etc. 113 | }, 114 | 115 | /* 116 | * Given an pointer to a class instance, return its type object. 117 | * Return null if no type object is found. 118 | */ 119 | identify: function (p) { 120 | if (p == 0) 121 | return null; 122 | if (this._idToType.hasOwnProperty(_mem_int32[p>>2])) 123 | return this._idToType[_mem_int32[p>>2]]; 124 | return null; 125 | }, 126 | 127 | // Map of class type IDs to type objects. 128 | 129 | _idToType: {}, 130 | 131 | _badType: function (self) { 132 | var t = this.identify(self); 133 | return new Error("Observed type: " + (t ? t.NAME : "*invalid*") + ", address=" + self); 134 | }, 135 | 136 | // Synchronic layout is 8 bytes (2 x int32) of metadata followed by 137 | // the type-specific payload. The two int32 words are the number 138 | // of waiters and the wait word (generation count). 139 | // 140 | // In the following: 141 | // 142 | // self is the base address for the Synchronic. 143 | // mem is the array to use for the value 144 | // idx is the index in mem of the value: (p+8)>>log2(mem.BYTES_PER_ELEMENT) 145 | // 146 | // _synchronicLoad is just Atomics.load, expand it in-line. 147 | 148 | _synchronicStore: function (self, mem, idx, value) { 149 | Atomics.store(mem, idx, value); 150 | this._notify(self); 151 | return value; 152 | }, 153 | 154 | _synchronicCompareExchange: function (self, mem, idx, oldval, newval) { 155 | var v = Atomics.compareExchange(mem, idx, oldval, newval); 156 | if (v == oldval) 157 | this._notify(self); 158 | return v; 159 | }, 160 | 161 | _synchronicAdd: function (self, mem, idx, value) { 162 | var v = Atomics.add(mem, idx, value); 163 | this._notify(self); 164 | return v; 165 | }, 166 | 167 | _synchronicSub: function (self, mem, idx, value) { 168 | var v = Atomics.sub(mem, idx, value); 169 | this._notify(self); 170 | return v; 171 | }, 172 | 173 | _synchronicAnd: function (self, mem, idx, value) { 174 | var v = Atomics.and(mem, idx, value); 175 | this._notify(self); 176 | return v; 177 | }, 178 | 179 | _synchronicOr: function (self, mem, idx, value) { 180 | var v = Atomics.or(mem, idx, value); 181 | this._notify(self); 182 | return v; 183 | }, 184 | 185 | _synchronicXor: function (self, mem, idx, value) { 186 | var v = Atomics.xor(mem, idx, value); 187 | this._notify(self); 188 | return v; 189 | }, 190 | 191 | _synchronicLoadWhenNotEqual: function (self, mem, idx, value) { 192 | for (;;) { 193 | var tag = Atomics.load(_mem_int32, (self+4)>>2); 194 | var v = Atomics.load(mem, idx) ; 195 | if (v !== value) 196 | break; 197 | this._waitForUpdate(self, tag, Number.POSITIVE_INFINITY); 198 | } 199 | return v; 200 | }, 201 | 202 | _synchronicLoadWhenEqual: function (self, mem, idx, value) { 203 | for (;;) { 204 | var tag = Atomics.load(_mem_int32, (self+4)>>2); 205 | var v = Atomics.load(mem, idx) ; 206 | if (v === value) 207 | break; 208 | this._waitForUpdate(self, tag, Number.POSITIVE_INFINITY); 209 | } 210 | return v; 211 | }, 212 | 213 | _synchronicExpectUpdate: function (self, mem, idx, value, timeout) { 214 | var now = this._now(); 215 | var limit = now + timeout; 216 | for (;;) { 217 | var tag = Atomics.load(_mem_int32, (self+4)>>2); 218 | var v = Atomics.load(mem, idx) ; 219 | if (v !== value || now >= limit) 220 | break; 221 | this._waitForUpdate(self, tag, limit - now); 222 | now = this._now(); 223 | } 224 | }, 225 | 226 | _waitForUpdate: function (self, tag, timeout) { 227 | // Spin for a short time before going into the futexWait. 228 | // 229 | // Hard to know what a good count should be - it is machine 230 | // dependent, for sure, and "typical" applications should 231 | // influence the choice. If the count is high without 232 | // hindering an eventual drop into futexWait then it will just 233 | // decrease performance. If the count is low it is pointless. 234 | // (This is why Synchronic really wants a native implementation.) 235 | // 236 | // Data points from a 2.6GHz i7 MacBook Pro: 237 | // 238 | // - the simple send-integer benchmark (test-sendint.html), 239 | // which is the very simplest case we can really imagine, 240 | // gets noisy timings with an iteration count below 4000 241 | // 242 | // - the simple send-object benchmark (test-sendmsg.html) 243 | // gets a boost when the count is at least 10000 244 | // 245 | // 10000 is perhaps 5us (CPI=1, naive) and seems like a 246 | // reasonable cutoff, for now - but note, it is reasonable FOR 247 | // THIS SYSTEM ONLY, which is a big flaw. 248 | // 249 | // The better fix might well be to add some kind of spin/nanosleep 250 | // functionality to futexWait, see https://bugzil.la/1134973. 251 | // That functionality can be platform-dependent and even 252 | // adaptive, with JIT support. 253 | var i = 10000; 254 | do { 255 | // May want this to be a relaxed load, though on x86 it won't matter. 256 | if (Atomics.load(_mem_int32, (self+4)>>2) != tag) 257 | return; 258 | } while (--i > 0); 259 | Atomics.add(_mem_int32, self>>2, 1); 260 | Atomics.futexWait(_mem_int32, (self+4)>>2, tag, timeout); 261 | Atomics.sub(_mem_int32, self>>2, 1); 262 | }, 263 | 264 | _notify: function (self) { 265 | Atomics.add(_mem_int32, (self+4)>>2, 1); 266 | // Would it be appropriate & better to wake n waiters, where n 267 | // is the number loaded in the load()? I almost think so, 268 | // since our futexes are fair. 269 | if (Atomics.load(_mem_int32, self>>2) > 0) 270 | Atomics.futexWake(_mem_int32, (self+4)>>2, Number.POSITIVE_INFINITY); 271 | }, 272 | 273 | _now: (typeof 'performance' != 'undefined' && typeof performance.now == 'function' 274 | ? performance.now.bind(performance) 275 | : Date.now.bind(Date)) 276 | }; 277 | 278 | // FIXME: Apart from the allocator and the atomic store this is the same 279 | // as the ArrayBuffer version; merge them. 280 | 281 | function _FlatJS_init_sab(flatjs, sab, start, limit, initialize) { 282 | if ((start|0) != start || (limit|0) != limit) 283 | throw new Error("Invalid bounds: " + start + " " + limit); 284 | start = (start + 7) & ~7; 285 | limit = (limit & ~7); 286 | if (start < 0 || limit <= start || limit > sab.byteLength) 287 | throw new Error("Invalid bounds: " + start + " " + limit); 288 | var len = (limit - start); 289 | if (len < 16) 290 | throw new Error("The memory is too small even for metadata: " + sab.byteLength); 291 | flatjs.alloc = _FlatJS_alloc_sab; 292 | _mem_int8 = new Int8Array(sab, start, len); 293 | _mem_uint8 = new Uint8Array(sab, start, len); 294 | _mem_int16 = new Int16Array(sab, start, len/2); 295 | _mem_uint16 = new Uint16Array(sab, start, len/2); 296 | _mem_int32 = new Int32Array(sab, start, len/4); 297 | _mem_uint32 = new Uint32Array(sab, start, len/4); 298 | _mem_float32 = new Float32Array(sab, start, len/4); 299 | _mem_float64 = new Float64Array(sab, start, len/8); 300 | if (initialize) { 301 | _mem_int32[2] = len; 302 | Atomics.store(_mem_int32, 1, 16); 303 | } 304 | } 305 | 306 | function _FlatJS_init_ab(flatjs, ab, start, limit, initialize) { 307 | if ((start|0) != start || (limit|0) != limit) 308 | throw new Error("Invalid bounds: " + start + " " + limit); 309 | start = (start + 7) & ~7; 310 | limit = (limit & ~7); 311 | if (start < 0 || limit <= start || limit > ab.byteLength) 312 | throw new Error("Invalid bounds: " + start + " " + limit); 313 | var len = (limit - start); 314 | if (len < 16) 315 | throw new Error("The memory is too small even for metadata"); 316 | flatjs.alloc = _FlatJS_alloc_ab; 317 | _mem_int8 = new Int8Array(ab, start, len); 318 | _mem_uint8 = new Uint8Array(ab, start, len); 319 | _mem_int16 = new Int16Array(ab, start, len/2); 320 | _mem_uint16 = new Uint16Array(ab, start, len/2); 321 | _mem_int32 = new Int32Array(ab, start, len/4); 322 | _mem_uint32 = new Uint32Array(ab, start, len/4); 323 | _mem_float32 = new Float32Array(ab, start, len/4); 324 | _mem_float64 = new Float64Array(ab, start, len/8); 325 | if (initialize) { 326 | _mem_int32[2] = len; 327 | _mem_int32[1] = 16; 328 | } 329 | } 330 | 331 | // For allocators: Do not round up nbytes, for now. References to 332 | // fields within structures can be to odd addresses and there's no 333 | // particular reason that an object can't be allocated on an odd 334 | // address. (Later, with a header or similar info, it will be 335 | // different.) 336 | 337 | // Note, actual zero-initialization is not currently necessary 338 | // since the buffer must be zero-initialized by the client code 339 | // and this is a simple bump allocator. 340 | 341 | function _FlatJS_alloc_sab(nbytes, alignment) { 342 | do { 343 | var p = Atomics.load(_mem_int32, 1); 344 | var q = (p + (alignment-1)) & ~(alignment - 1); 345 | var top = q + nbytes; 346 | if (top >= _mem_int32[2]) 347 | return 0; 348 | } while (Atomics.compareExchange(_mem_int32, 1, p, top) != p); 349 | return q; 350 | } 351 | 352 | function _FlatJS_alloc_ab(nbytes, alignment) { 353 | var p = _mem_int32[1]; 354 | p = (p + (alignment-1)) & ~(alignment - 1); 355 | var top = p + nbytes; 356 | if (top >= _mem_int32[2]) 357 | return 0; 358 | _mem_int32[1] = top; 359 | return p; 360 | } 361 | 362 | var NULL = 0; 363 | var int8 = { SIZE:1, ALIGN:1, NAME:"int8" }; 364 | var uint8 = { SIZE:1, ALIGN:1, NAME:"uint8" }; 365 | var int16 = { SIZE:2, ALIGN:2, NAME:"int16" }; 366 | var uint16 = { SIZE:2, ALIGN:2, NAME:"uint16" }; 367 | var int32 = { SIZE:4, ALIGN:4, NAME:"int32" }; 368 | var uint32 = { SIZE:4, ALIGN:4, NAME:"uint32" }; 369 | var float32 = { SIZE:4, ALIGN:4, NAME:"float32" }; 370 | var float64 = { SIZE:8, ALIGN:8, NAME:"float64" }; 371 | var int32x4 = { SIZE:16, ALIGN:16, NAME:"int32x4" }; 372 | var float32x4 = { SIZE:16, ALIGN:16, NAME:"float32x4" }; 373 | var float64x2 = { SIZE:16, ALIGN:16, NAME:"float64x2" }; 374 | 375 | function MemoryError(msg) { 376 | this.message = msg; 377 | } 378 | MemoryError.prototype = new Error("Memory Error"); 379 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | JSSHELL=../../mozilla-shmem/js/src/build-release/dist/bin/js 2 | FJSC=nodejs ../fjsc.js 3 | 4 | test: 5 | $(FJSC) basic-tests.flat_js 6 | $(JSSHELL) basic-tests.js 7 | $(FJSC) atomic-tests.flat_js 8 | $(JSSHELL) atomic-tests.js 9 | $(FJSC) synchronic-tests.flat_js 10 | $(JSSHELL) synchronic-tests.js 11 | $(FJSC) simd-tests.flat_js 12 | $(JSSHELL) simd-tests.js 13 | $(FJSC) typescript-tests.flat_ts 14 | -------------------------------------------------------------------------------- /test/atomic-tests.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | load("../libflatjs.js"); 4 | var ab = new SharedArrayBuffer(65536); 5 | FlatJS.init(ab, 0, ab.byteLength, true); 6 | 7 | // Atomic fields of class 8 | 9 | @flatjs class Counter { 10 | count: int32.atomic 11 | xyzzy: uint8.atomic 12 | 13 | @method init(SELF, x) { 14 | SELF.count.set(x); // Atomics.store 15 | return SELF; 16 | } 17 | 18 | @method fnord(SELF) { 19 | SELF.count = 0; 20 | SELF.count += 5; 21 | } 22 | 23 | } @end 24 | 25 | var c = Counter.init(@new Counter, 7); 26 | 27 | Counter.count.add(c, 1); 28 | assertEq(Counter.count(c), 8); // Atomics.load, here and below 29 | 30 | Counter.count.compareExchange(c, 8, 4); 31 | assertEq(Counter.count(c), 4); 32 | 33 | Counter.count.sub(c, 1); 34 | assertEq(Counter.count(c), 3); 35 | 36 | Counter.count.or(c, 0xB1); 37 | assertEq(Counter.count(c), 0xB3); 38 | 39 | Counter.count.and(c, 0x1A2); 40 | assertEq(Counter.count(c), 0xA2); 41 | 42 | Counter.count.xor(c, 0xFF); 43 | assertEq(Counter.count(c), 0x5D); 44 | 45 | Counter.xyzzy.add(c, 384); 46 | assertEq(Counter.xyzzy(c), 384%256); 47 | 48 | // Atomic fields of structures within array 49 | 50 | @flatjs struct Cnt { 51 | count: int32.atomic 52 | } @end 53 | 54 | var p = @new Cnt.Array(10); 55 | 56 | Cnt.Array.count.add(p, 5, 1); 57 | assertEq(Cnt.Array.count.at(p, 5), 1); 58 | 59 | console.log("Done"); 60 | -------------------------------------------------------------------------------- /test/basic-tests.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | load("../libflatjs.js"); 4 | var ab = new ArrayBuffer(1024); 5 | FlatJS.init(ab, 0, ab.byteLength, true); 6 | 7 | // Basic ideas 8 | 9 | assertEq(int8.SIZE, 1); 10 | assertEq(int16.SIZE, 2); 11 | assertEq(int32.SIZE, 4); 12 | 13 | assertEq(uint8.SIZE, 1); 14 | assertEq(uint16.SIZE, 2); 15 | assertEq(uint32.SIZE, 4); 16 | 17 | assertEq(float32.SIZE, 4); 18 | assertEq(float64.SIZE, 8); 19 | 20 | // Basic classes 21 | 22 | var glob = 0; 23 | 24 | @flatjs class Point { 25 | x:int32 26 | y:int32 27 | 28 | @method init(SELF) { 29 | SELF.x = SELF.y = 0; // Shorthand syntax 30 | SELF.y += 5; // Shorthand syntax 31 | } 32 | 33 | @virtual deep6(SELF) { 34 | SELF.x.set(-SELF.x); 35 | SELF.y.set(-SELF.y); 36 | SELF.deep7(86); 37 | } 38 | 39 | @method deep7(SELF, k) { 40 | glob = k; 41 | } 42 | } @end 43 | 44 | assertEq(typeof Point, "function"); 45 | assertEq((new Point) instanceof Point, true); 46 | 47 | assertEq(Point.NAME, "Point"); 48 | assertEq(Point.SIZE, 12); // White-box, it could be larger 49 | assertEq(Point.ALIGN, 4); // White-box, it could be larger 50 | assertEq(typeof Point.CLSID, "number"); 51 | assertEq(Point.BASE, null); 52 | 53 | var p = @new Point; 54 | Point.x.set(p, 10); 55 | Point.y.set(p, 20); 56 | assertEq(Point.x(p), 10); 57 | assertEq(Point.y(p), 20); 58 | 59 | var po = new Point(p); 60 | assertEq(po.pointer, p); 61 | 62 | assertEq(FlatJS.identify(p), Point); 63 | assertEq(Point.CLSID, _mem_int32[p>>2]); // White-box, layout 64 | 65 | assertEq(Point.x.ref(p) + int32.SIZE, Point.y.ref(p)); // Gray-box, layout 66 | 67 | Point.deep6(p); 68 | assertEq(glob, 86); 69 | 70 | // Sundry syntax corner cases 71 | 72 | @flatjs class EmptyClass { 73 | } @end 74 | 75 | @flatjs class MethodOnlyClass { 76 | @method oneLiner(SELF) { return 1; } 77 | @method anotherOneLiner(SELF) { return 2; } 78 | } @end 79 | 80 | // Basic inheritance 81 | 82 | var XSELF = { x: 10, z: { ref: -99 } }; 83 | var YSELF = { y: 20, z: { set: function (v) { g_probe2 = v } } }; 84 | var g_probe = 0; 85 | var g_probe2 = 0; 86 | var ZSELF = { deep6: function () { g_probe3 = "here"; } }; 87 | var g_probe3 = "there"; 88 | 89 | @flatjs class Point3D extends Point { 90 | z:int32 91 | 92 | @method init(SELF, x, y, z) { 93 | SELF.x.set(x); 94 | SELF.y.set(y); 95 | SELF.z.set(z); 96 | XSELF.x = 37; // Should not be expanded 97 | YSELF.y = XSELF.x; // Ditto 98 | g_probe = XSELF.z.ref; // Ditto 99 | YSELF.z.set(10); // Ditto 100 | ZSELF.deep6(); // Ditto 101 | return SELF; 102 | } 103 | 104 | @virtual deep6(SELF) { // Override 105 | SELF.z = -SELF.z; // Alternate syntax 106 | } 107 | } @end 108 | 109 | assertEq(Point3D.NAME, "Point3D"); 110 | 111 | assertEq(typeof Point3D, "function"); 112 | assertEq((new Point3D) instanceof Point3D, true); 113 | assertEq((new Point3D) instanceof Point, true); 114 | 115 | var q = Point3D.init(@new Point3D, 5, 7, 8); 116 | assertEq(Point.x(q), 5); 117 | assertEq(Point.y(q), 7); 118 | assertEq(Point3D.x(q), 5); 119 | assertEq(Point3D.y(q), 7); 120 | assertEq(Point3D.z(q), 8); 121 | 122 | assertEq(XSELF.x, 37); 123 | assertEq(YSELF.y, 37); 124 | assertEq(g_probe, -99); 125 | assertEq(g_probe2, 10); 126 | assertEq(g_probe3, "here"); 127 | 128 | var qo = new Point3D(q); 129 | assertEq(qo.pointer, q); 130 | 131 | // Virtual calls 132 | 133 | Point3D.deep6(q); // Invoke Point3D.deep6 134 | assertEq(Point3D.x(q), 5); 135 | assertEq(Point3D.y(q), 7); 136 | assertEq(Point3D.z(q), -8); 137 | 138 | Point.deep6(q); // Virtual, so invoke Point3D.deep6 139 | assertEq(Point3D.x(q), 5); 140 | assertEq(Point3D.y(q), 7); 141 | assertEq(Point3D.z(q), 8); 142 | 143 | assertEq(FlatJS.identify(q), Point3D); 144 | assertEq(Point3D.BASE, Point); 145 | 146 | // Structures 147 | 148 | @flatjs struct Pair { 149 | x:float64; 150 | y:int32; 151 | 152 | @set(SELF, v) { 153 | SELF.x.set(v.x); 154 | SELF.y.set(v.y); 155 | } 156 | @get(SELF) { 157 | return {x:SELF.x, y:SELF.y}; 158 | } 159 | } @end 160 | 161 | assertEq(Pair.SIZE, 16); 162 | assertEq(Pair.ALIGN, 8); // White-box, it could be larger 163 | assertEq(Pair.NAME, "Pair"); 164 | 165 | assertEq(typeof Pair, "function"); 166 | assertEq((new Pair) instanceof Pair, true); 167 | 168 | // Structure fields within classes 169 | 170 | @flatjs class PairBox { 171 | pad1:float32; 172 | pad2:float64; 173 | p:Pair; 174 | pad3:int32; 175 | } @end 176 | 177 | var pr = @new PairBox; 178 | PairBox.p.x.set(pr, 10); 179 | PairBox.p.y.set(pr, 20); 180 | 181 | PairBox.pad1.set(pr, 1); 182 | PairBox.pad2.set(pr, 2); 183 | PairBox.pad3.set(pr, 3); 184 | 185 | assertEq(PairBox.pad1.ref(pr), pr+4); // White-box, layout 186 | 187 | var ppr = PairBox.p.ref(pr); 188 | assertEq(Pair.x.ref(ppr) - ppr, 0); // Well-defined 189 | assertEq(Pair.y.ref(ppr) - ppr, 8); // White-box, it could be larger though shouldn't 190 | 191 | assertEq(PairBox.p.x(pr), 10); 192 | assertEq(PairBox.p.y(pr), 20); 193 | 194 | assertEq(PairBox.pad1(pr), 1); 195 | assertEq(PairBox.pad2(pr), 2); 196 | assertEq(PairBox.pad3(pr), 3); 197 | 198 | var ia = @new int32.Array(7); 199 | for ( var i=0 ; i < 7 ; i++ ) 200 | int32.Array.setAt(ia, i, -i); 201 | 202 | for ( var i=0 ; i < 7 ; i++ ) 203 | assertEq(int32.Array.at(ia, i), -i|0); 204 | 205 | var iar = int32.Array.ref(ia, 3); 206 | assertEq(iar, ia+3*int32.SIZE); // White-box? Layout. 207 | 208 | // This also tests comments within parameter lists 209 | var pa = @new Pair.Array(4); 210 | for ( var i=0 ; i < 4 ; i++ ) { 211 | Pair.Array.x.setAt(pa, i, /* value */ i); 212 | Pair.Array.y.setAt(pa, i, /* value */ -i); 213 | } 214 | 215 | for ( var i=0 ; i < 4 ; i++ ) { 216 | assertEq(Pair.Array.x.at(pa, i), i); 217 | assertEq(Pair.Array.y.at(pa, i), -i|0); 218 | } 219 | 220 | for ( var i=0 ; i < 4 ; i++ ) 221 | var v = Pair.Array.setAt(pa, i, {x:i+5, y:i-5}); 222 | 223 | for ( var i=0 ; i < 4 ; i++ ) { 224 | var v = Pair.Array.at(pa, i); 225 | assertEq(v.x, i+5); 226 | assertEq(v.y, i-5); 227 | } 228 | 229 | var xr = Pair.Array.ref(pa, 4); 230 | assertEq(xr, pa+4*Pair.SIZE); // White-box? Layout. 231 | 232 | // This tests strings within parameter lists. Conversion should let 233 | // the program run. 234 | Pair.Array.x.setAt(pa, i, "37\"42"); 235 | 236 | @flatjs class WithArray { 237 | xs : int32.Array 238 | } @end 239 | 240 | // Default getter and setter 241 | 242 | @flatjs struct Pair2 { 243 | x:float64; 244 | y:int32; 245 | } @end 246 | 247 | @flatjs class Pair2Box { 248 | p: Pair2; 249 | } @end 250 | 251 | var p2 = @new Pair2Box; 252 | Pair2Box.p.set(p2, { x:-1.5, y:37} ); 253 | assertEq(Pair2Box.p(p2).x, -1.5); 254 | assertEq(Pair2Box.p(p2).y, 37); 255 | 256 | // Alloc primitive types 257 | 258 | var ip = @new int32; 259 | int32.set(ip,12); 260 | assertEq(int32.get(ip), 12); 261 | 262 | var p2p = @new Pair2; 263 | Pair2.set(p2p, {x:-2.2, y:42}); 264 | assertEq(Pair2.get(p2p).x, -2.2); 265 | assertEq(Pair2.get(p2p).y, 42); 266 | 267 | print("Done"); 268 | -------------------------------------------------------------------------------- /test/simd-tests.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | load("../libflatjs.js"); 4 | var ab = new ArrayBuffer(1024); 5 | FlatJS.init(ab, 0, ab.byteLength, true); 6 | 7 | var p = @new float32x4.Array(10); 8 | 9 | float32x4.Array.setAt(p, 7, SIMD.float32x4(1,2,3,4)); 10 | assertEq(float32x4.Array.at(p, 7).x, 1); 11 | assertEq(float32x4.Array.at(p, 7).y, 2); 12 | assertEq(float32x4.Array.at(p, 7).z, 3); 13 | assertEq(float32x4.Array.at(p, 7).w, 4); 14 | 15 | float32x4.Array.setAt(p, 4, SIMD.float32x4(2,4,8,12)); 16 | assertEq(float32x4.Array.at(p, 4).x, 2); 17 | assertEq(float32x4.Array.at(p, 4).y, 4); 18 | assertEq(float32x4.Array.at(p, 4).z, 8); 19 | assertEq(float32x4.Array.at(p, 4).w, 12); 20 | 21 | var v = SIMD.float32x4.sub(float32x4.Array.at(p, 7), float32x4.Array.at(p, 4)); 22 | 23 | // Does not work for unknown reasons 24 | //assertEq([v.x, v.y, v.z, v.w], [-1, -2, -5, -8]); 25 | assertEq(v.x, -1); 26 | assertEq(v.y, -2); 27 | assertEq(v.z, -5); 28 | assertEq(v.w, -8); 29 | 30 | // TODO: Test that SELF assignment shorthands work (only = should be defined). 31 | 32 | print("Done.") 33 | -------------------------------------------------------------------------------- /test/synchronic-tests.flat_js: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | load("../libflatjs.js"); 4 | var ab = new SharedArrayBuffer(65536); 5 | FlatJS.init(ab, 0, ab.byteLength, true); 6 | 7 | // Synchronic fields of class 8 | 9 | @flatjs class Counter { 10 | count:int32.synchronic 11 | xyzzy:uint8.synchronic 12 | 13 | @method init(SELF, x) { 14 | SELF.count.set(x); // synchronic store 15 | return SELF; 16 | } 17 | 18 | // Should turn into synchronic ops. 19 | 20 | @method fnord(SELF) { 21 | SELF.count = 0; 22 | SELF.count += 5; 23 | } 24 | } @end 25 | 26 | var c = Counter.init(@new Counter, 7); 27 | 28 | Counter.count.add(c, 1); 29 | assertEq(Counter.count(c), 8); // Atomics.load, here and below 30 | 31 | Counter.count.compareExchange(c, 8, 4); 32 | assertEq(Counter.count(c), 4); 33 | 34 | Counter.count.sub(c, 1); 35 | assertEq(Counter.count(c), 3); 36 | 37 | Counter.count.or(c, 0xB1); 38 | assertEq(Counter.count(c), 0xB3); 39 | 40 | Counter.count.and(c, 0x1A2); 41 | assertEq(Counter.count(c), 0xA2); 42 | 43 | Counter.count.xor(c, 0xFF); 44 | assertEq(Counter.count(c), 0x5D); 45 | 46 | Counter.xyzzy.add(c, 384); 47 | assertEq(Counter.xyzzy(c), 384%256); 48 | 49 | // Synchronic fields of structures within array 50 | 51 | @flatjs struct Cnt { 52 | count:int32.synchronic 53 | } @end 54 | 55 | var p = @new Cnt.Array(10); 56 | 57 | Cnt.Array.count.add(p, 5, 1); 58 | assertEq(Cnt.Array.count.at(p, 5), 1); 59 | 60 | setSharedArrayBuffer(ab); 61 | Counter.count.set(c, 0); 62 | 63 | evalInWorker(` 64 | load("../libflatjs.js"); 65 | var ab = getSharedArrayBuffer(); 66 | FlatJS.init(ab, 0, ab.byteLength, false); 67 | 68 | var c = ${c}; 69 | 70 | // This is gross. Counter is defined in *this file* so its macro 71 | // definitions are visible here and will be replaced, and that's 72 | // enough to make this test work. But if we had methods to invoke 73 | // they would not work, because the Counter object is not visible 74 | // within the worker's code. 75 | // 76 | // That would normally be fixed by putting the worker program in a 77 | // file and just having a one-liner program here, to load that file. 78 | // That file could be preprocessed independently. 79 | 80 | Counter.count.loadWhenNotEqual(c, 0); 81 | var then = Date.now(); 82 | sleep(1); 83 | Counter.count.set(c, 2); 84 | console.log("Should be about 1000: " + (Date.now() - then)); 85 | `); 86 | 87 | sleep(1); 88 | var then = Date.now(); 89 | Counter.count.set(c, 1); 90 | Counter.count.loadWhenNotEqual(c, 1); 91 | console.log("Should be about 1000: " + (Date.now() - then)); 92 | 93 | console.log("Done"); 94 | -------------------------------------------------------------------------------- /test/typescript-tests.flat_ts: -------------------------------------------------------------------------------- 1 | /* -*- mode: javascript -*- */ 2 | 3 | load("../libflatjs.js"); 4 | var ab = new ArrayBuffer(1024); 5 | FlatJS.init(ab, true); 6 | 7 | @flatjs class TSTest { 8 | @virtual testMethod(SELF, x:number, y:string, ...z): void { 9 | return x + y + z.join(","); 10 | } 11 | } @end 12 | 13 | @flatjs class TSTest2 extends TSTest { 14 | @virtual testMethod(SELF, x:number, y:string, ...z): void { 15 | return x + y + z.join(","); 16 | } 17 | } @end 18 | -------------------------------------------------------------------------------- /tsd.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v4", 3 | "repo": "borisyankov/DefinitelyTyped", 4 | "ref": "master", 5 | "path": "typings", 6 | "bundle": "typings/tsd.d.ts", 7 | "installed": { 8 | "node/node.d.ts": { 9 | "commit": "f23cd55e319a0f67362ca0dbc4cf3e2ec1339f4e" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /typings/tsd.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | --------------------------------------------------------------------------------