diff --git a/LICENSE.llvm b/LICENSE.llvm new file mode 100644 index 0000000..24806ab --- /dev/null +++ b/LICENSE.llvm @@ -0,0 +1,278 @@ +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2007-2019 University of Illinois at Urbana-Champaign. +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. diff --git a/LICENSE.vasm b/LICENSE.vasm new file mode 100644 index 0000000..b992dd8 --- /dev/null +++ b/LICENSE.vasm @@ -0,0 +1,11 @@ +vasm is copyright in 2002-2019 by Volker Barthelmann. + +This archive may be redistributed without modifications and used for non-commercial purposes. + +An exception for commercial usage is granted, provided that the target CPU is M68k and the target OS is AmigaOS. Resulting binaries may be distributed commercially without further licensing. + +In all other cases you need my written consent. + +Certain modules may fall under additional copyrights. + +See http://sun.hasenbraten.de/vasm/release/vasm.html diff --git a/package.json b/package.json index 46d1429..fe423e7 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,14 @@ "dependencies": { "@angular/animations": "15.2.5", "@angular/cdk": "15.2.5", + "@angular/common": "15.2.5", + "@angular/compiler": "15.2.5", + "@angular/core": "15.2.5", + "@angular/elements": "15.2.5", + "@angular/flex-layout": "15.0.0-beta.42", + "@angular/forms": "15.2.5", + "@angular/material": "15.2.5", + "@angular/platform-browser": "15.2.5", "@angular/platform-browser-dynamic": "15.2.5", "@angular/platform-server": "15.2.5", "@angular/router": "15.2.5", @@ -24,19 +32,8 @@ "@babel/types": "7.21.4", "@chainsafe/libp2p-noise": "12.0.0", "@chainsafe/libp2p-yamux": "4.0.1", + "@codemirror/lang-cpp": "^6.0.2", "@codemirror/lang-html": "6.4.3", - "@angular/common": "15.2.5", - "@editorjs/list": "1.8.0", - "@editorjs/marker": "1.3.0", - "@editorjs/table": "2.2.2", - "@editorjs/warning": "1.3.0", - "@angular/compiler": "15.2.5", - "@angular/core": "15.2.5", - "@angular/elements": "15.2.5", - "@angular/flex-layout": "15.0.0-beta.42", - "@angular/forms": "15.2.5", - "@angular/material": "15.2.5", - "@angular/platform-browser": "15.2.5", "@codemirror/lang-javascript": "6.2.1", "@codemirror/lang-python": "6.1.2", "@codemirror/lang-sql": "6.5.4", @@ -48,6 +45,10 @@ "@editorjs/editorjs": "2.29.0-rc.4", "@editorjs/embed": "2.6.0", "@editorjs/header": "https://github.com/sanchezcarlosjr/header.git", + "@editorjs/list": "1.8.0", + "@editorjs/marker": "1.3.0", + "@editorjs/table": "2.2.2", + "@editorjs/warning": "1.3.0", "@fortawesome/fontawesome-free": "6.4.0", "@gear-js/api": "0.32.3", "@jsonforms/angular": "3.0.0", diff --git a/src/app/notebook/cellTypes/CodeBlock.ts b/src/app/notebook/cellTypes/CodeBlock.ts index 4ce0e5b..f5cd046 100644 --- a/src/app/notebook/cellTypes/CodeBlock.ts +++ b/src/app/notebook/cellTypes/CodeBlock.ts @@ -8,6 +8,7 @@ import {stringToHTML} from "./stringToHTML"; import {Html} from "./languages/Html"; import { Sql } from "./languages/Sql"; import { JavaScriptMainThread } from "./languages/JavaScriptMainThread"; +import {Cpp} from "./languages/Cpp"; // @ts-ignore @@ -28,6 +29,7 @@ export class CodeBlock extends InteractiveBlock { private language: Language; private readonly outputCell: string; private code: string; + private languages = ["javascript", "python", "html", 'sql', 'javascript webworker', "cpp"]; constructor(private editorJsTool: EditorJsTool) { super(editorJsTool); @@ -43,6 +45,7 @@ export class CodeBlock extends InteractiveBlock { .with("python", () => new Python(this.code, this.editorJsTool, this.cell)) .with("html", () => new Html(this.code, this.editorJsTool, this.cell)) .with("sql", () => new Sql(this.code, this.editorJsTool, this.cell)) + .with("cpp", () => new Cpp(this.code, this.editorJsTool, this.cell)) .with( "javascript webworker", () => new JavaScript(this.code, this.editorJsTool, this.cell) ) @@ -273,7 +276,7 @@ export class CodeBlock extends InteractiveBlock { const wrapper = super.renderSettings(); let languagesSelect = document.createElement("select"); languagesSelect.classList.add("small"); - for (const language of ["javascript", "python", "html", 'sql', 'javascript webworker']) { + for (const language of this.languages) { const option = document.createElement("option"); option.value = language; option.innerText = language; diff --git a/src/app/notebook/cellTypes/languages/Cpp.ts b/src/app/notebook/cellTypes/languages/Cpp.ts new file mode 100644 index 0000000..8a2ff94 --- /dev/null +++ b/src/app/notebook/cellTypes/languages/Cpp.ts @@ -0,0 +1,51 @@ +import {Language} from "./language"; +import {Observable, shareReplay} from "rxjs"; +import {Extension} from "@codemirror/state"; +import {cpp} from "@codemirror/lang-cpp"; +import {autocompletion, CompletionContext, CompletionResult} from "@codemirror/autocomplete" +import {API} from './cpp/api'; +function autocompleteCpp(context: CompletionContext): CompletionResult | null { + let word = context.matchBefore(/\w*/) + if (!word || word.from == word.to && !context.explicit) + return null + return { + from: word.from, + options: [ + {label: "include", type: "include", apply: "#include", detail: "include library"}, + {label: "stdio.h", type: "include", apply: "#include ", detail: "include library"}, + {label: "function", type: "function", apply: `int f() { + return 0; + }`, detail: "function"}, + {label: "int", type: "function", apply: `int f() { + return 0; + }`, detail: "function"}, + {label: "int", type: "variable", apply: "int x=0;",detail: "create a int variable"}, + {label: "hello world", type: "text", apply: `#include +int main() { + printf("Hello World"); + return 0; +}`, detail: "macro"} + ] + } +} + +export class Cpp extends Language { + get name() { + return 'cpp'; + } + + override dispatchShellRun() { + const api = new API({ + hostWrite: (output: string) => this.write(output), + hostRead: () => prompt() + }); + this.clear(); + api.compileLinkRun(this.mostRecentCode).then(console.log).catch(console.error); + return true; + } + + override getExtensions(): Extension[] { + return [cpp(), autocompletion({ override: [autocompleteCpp] })]; + } + +} diff --git a/src/app/notebook/cellTypes/languages/Python.ts b/src/app/notebook/cellTypes/languages/Python.ts index 8acd4c4..28b83a4 100644 --- a/src/app/notebook/cellTypes/languages/Python.ts +++ b/src/app/notebook/cellTypes/languages/Python.ts @@ -12,7 +12,6 @@ function jsonStringifyToObjectReplacer(key: string, value: any) { return value.toObject(); } if (value && value.toJs) { - console.log(value.toString()); return value.toString(); } if (value && value.toJSON) { diff --git a/src/app/notebook/cellTypes/languages/cpp/api.js b/src/app/notebook/cellTypes/languages/cpp/api.js new file mode 100644 index 0000000..6467f49 --- /dev/null +++ b/src/app/notebook/cellTypes/languages/cpp/api.js @@ -0,0 +1,166 @@ +import { App } from './app' +import { MemFS } from './memfs' +import { msToSec } from './shared' +import { Tar } from './tar' + +const clangUrl = '/assets/clang/clang.wasm'; +const lldUrl = '/assets/clang/lld.wasm'; +const sysrootUrl = '/assets/clang/sysroot.tar'; + +export class API { + constructor(options) { + this.moduleCache = {} + this.hostWrite = options.hostWrite + this.showTiming = options.showTiming || true + + this.clangCommonArgs = [ + '-disable-free', + '-isysroot', + '/', + '-internal-isystem', + '/include/c++/v1', + '-internal-isystem', + '/include', + '-internal-isystem', + '/lib/clang/8.0.1/include', + '-ferror-limit', + '19', + '-fmessage-length', + '80', + '-fcolor-diagnostics', + ] + + this.memfs = new MemFS({ + hostWrite: this.hostWrite, + hostRead: options.hostRead + }) + this.ready = this.memfs.ready.then(() => { + return this.untar(this.memfs, sysrootUrl) + }) + } + + async getModule(name) { + if (this.moduleCache[name]) return this.moduleCache[name] + const module = await this.hostLogAsync( + `Fetching and compiling ${name}`, + (async () => { + const response = await fetch(name) + return WebAssembly.compile(await response.arrayBuffer()) + })() + ) + this.moduleCache[name] = module + return module + } + + hostLog(message) { + const yellowArrow = '\x1b[1;93m>\x1b[0m '; + this.log(`${yellowArrow}${message}`); + } + + log(message) { + console.log(message); + } + + async hostLogAsync(message, promise) { + const start = +new Date() + this.hostLog(`${message}...`) + const result = await promise + const end = +new Date() + this.log(' done.') + if (this.showTiming) { + const green = '\x1b[92m' + const normal = '\x1b[0m' + this.log(` ${green}(${msToSec(start, end)}s)${normal}`) + } + return result + } + + async untar(memfs, url) { + await this.memfs.ready + const promise = (async () => { + const tar = new Tar(await fetch(url).then((result) => result.arrayBuffer())) + tar.untar(this.memfs) + })() + await this.hostLogAsync(`Untarring ${url}`, promise) + } + + async compile(options) { + const input = options.input + const contents = options.contents + const obj = options.obj + const opt = options.opt || '2' + + await this.ready + this.memfs.addFile(input, contents) + const clang = await this.getModule(clangUrl) + return await this.run( + clang, + 'clang', + '-cc1', + '-emit-obj', + ...this.clangCommonArgs, + '-O2', + '-o', + obj, + '-x', + 'c++', + input + ) + } + + async link(obj, wasm) { + const stackSize = 1024 * 1024 + + const libdir = 'lib/wasm32-wasi' + const crt1 = `${libdir}/crt1.o` + + await this.ready + const lld = await this.getModule(lldUrl) + return await this.run( + lld, + 'wasm-ld', + '--no-threads', + '--export-dynamic', // TODO required? + '-z', + `stack-size=${stackSize}`, + `-L${libdir}`, + crt1, + obj, + '-lc', + '-lc++', + '-lc++abi', + '-o', + wasm + ) + } + + async run(module, ...args) { + this.hostLog(`${args.join(' ')}`) + const start = +new Date() + const app = new App(module, this.memfs, ...args) + const instantiate = +new Date() + const stillRunning = await app.run() + const end = +new Date() + if (this.showTiming) { + const green = '\x1b[92m' + const normal = '\x1b[0m' + let msg = `${green}(${msToSec(start, instantiate)}s` + msg += `/${msToSec(instantiate, end)}s)${normal}` + this.log(msg) + } + return stillRunning ? app : null + } + + async compileLinkRun(contents) { + const input = `test.cc` + const obj = `test.o` + const wasm = `test.wasm` + await this.compile({ input, contents, obj }) + await this.link(obj, wasm) + + const buffer = this.memfs.getFileContents(wasm) + const testMod = await WebAssembly.compile(buffer) + + return await this.run(testMod, wasm) + } +} diff --git a/src/app/notebook/cellTypes/languages/cpp/app.js b/src/app/notebook/cellTypes/languages/cpp/app.js new file mode 100644 index 0000000..2d089cc --- /dev/null +++ b/src/app/notebook/cellTypes/languages/cpp/app.js @@ -0,0 +1,136 @@ +import { NotImplemented, ProcExit } from './errors.js' +import { Memory } from './memory' +import { ESUCCESS, getImportObject, RAF_PROC_EXIT_CODE } from './shared' + +export class App { + constructor(module, memfs, name, ...args) { + this.argv = [name, ...args] + this.environ = { USER: 'alice' } + this.memfs = memfs + this.allowRequestAnimationFrame = true + this.handles = new Map() + this.nextHandle = 0 + + const env = getImportObject(this) + + const wasi_unstable = getImportObject(this, [ + 'proc_exit', + 'environ_sizes_get', + 'environ_get', + 'args_sizes_get', + 'args_get', + 'random_get', + 'clock_time_get', + 'poll_oneoff', + ]) + + // Fill in some WASI implementations from memfs. + Object.assign(wasi_unstable, this.memfs.exports) + + this.ready = WebAssembly.instantiate(module, { wasi_unstable, env }).then((instance) => { + this.exports = instance.exports + this.mem = new Memory(this.exports.memory) + this.memfs.hostMem = this.mem + }) + } + + async run() { + await this.ready + try { + this.exports._start() + } catch (exn) { + let writeStack = true + if (exn instanceof ProcExit) { + if (exn.code === RAF_PROC_EXIT_CODE) { + console.log('Allowing rAF after exit.') + return true + } + // Don't allow rAF unless you return the right code. + console.log(`Disallowing rAF since exit code is ${exn.code}.`) + this.allowRequestAnimationFrame = false + if (exn.code == 0) { + return false + } + writeStack = false + } + + // Write error message. + let msg = `Error: ${exn.message}` + if (writeStack) { + msg = msg + `\n${exn.stack}` + } + msg += '\x1b[0m\n' + this.memfs.hostWrite(msg) + + // Propagate error. + throw exn + } + } + + proc_exit(code) { + throw new ProcExit(code) + } + + environ_sizes_get(environ_count_out, environ_buf_size_out) { + this.mem.check() + let size = 0 + const names = Object.getOwnPropertyNames(this.environ) + for (const name of names) { + const value = this.environ[name] + // +2 to account for = and \0 in "name=value\0". + size += name.length + value.length + 2 + } + this.mem.write64(environ_count_out, names.length) + this.mem.write64(environ_buf_size_out, size) + return ESUCCESS + } + + environ_get(environ_ptrs, environ_buf) { + this.mem.check() + const names = Object.getOwnPropertyNames(this.environ) + for (const name of names) { + this.mem.write32(environ_ptrs, environ_buf) + environ_ptrs += 4 + environ_buf += this.mem.writeStr(environ_buf, `${name}=${this.environ[name]}`) + } + this.mem.write32(environ_ptrs, 0) + return ESUCCESS + } + + args_sizes_get(argc_out, argv_buf_size_out) { + this.mem.check() + let size = 0 + for (let arg of this.argv) { + size += arg.length + 1 // "arg\0". + } + this.mem.write64(argc_out, this.argv.length) + this.mem.write64(argv_buf_size_out, size) + return ESUCCESS + } + + args_get(argv_ptrs, argv_buf) { + this.mem.check() + for (let arg of this.argv) { + this.mem.write32(argv_ptrs, argv_buf) + argv_ptrs += 4 + argv_buf += this.mem.writeStr(argv_buf, arg) + } + this.mem.write32(argv_ptrs, 0) + return ESUCCESS + } + + random_get(buf, buf_len) { + const data = new Uint8Array(this.mem.buffer, buf, buf_len) + for (let i = 0; i < buf_len; ++i) { + data[i] = (Math.random() * 256) | 0 + } + } + + clock_time_get(clock_id, precision, time_out) { + throw new NotImplemented('wasi_unstable', 'clock_time_get') + } + + poll_oneoff(in_ptr, out_ptr, nsubscriptions, nevents_out) { + throw new NotImplemented('wasi_unstable', 'poll_oneoff') + } +} diff --git a/src/app/notebook/cellTypes/languages/cpp/errors.js b/src/app/notebook/cellTypes/languages/cpp/errors.js new file mode 100644 index 0000000..13af87b --- /dev/null +++ b/src/app/notebook/cellTypes/languages/cpp/errors.js @@ -0,0 +1,24 @@ +export class ProcExit extends Error { + constructor(code) { + super(`process exited with code ${code}.`) + this.code = code + } +} + +export class NotImplemented extends Error { + constructor(modname, fieldname) { + super(`${modname}.${fieldname} not implemented.`) + } +} + +export class AbortError extends Error { + constructor(msg = 'abort') { + super(msg) + } +} + +export class AssertError extends Error { + constructor(msg) { + super(msg) + } +} diff --git a/src/app/notebook/cellTypes/languages/cpp/memfs.js b/src/app/notebook/cellTypes/languages/cpp/memfs.js new file mode 100644 index 0000000..60d69d3 --- /dev/null +++ b/src/app/notebook/cellTypes/languages/cpp/memfs.js @@ -0,0 +1,132 @@ +import { AbortError } from './errors.js' +import { Memory } from './memory' +import { assert, ESUCCESS, getImportObject } from './shared' + +const memfsUrl = '/assets/clang/memfs.wasm'; + +export class MemFS { + constructor(options) { + this.hostWrite = options.hostWrite; + this.hostRead = options.hostRead; + this.stdinStr = options.stdinStr || ''; + this.stdinStrPos = 0; + + this.hostMem_ = null // Set later when wired up to application. + + // Imports for memfs module. + const env = getImportObject(this, ['abort', 'host_write', 'host_read', 'memfs_log', 'copy_in', 'copy_out']) + + this.ready = fetch(memfsUrl) + .then((result) => result.arrayBuffer()) + .then(async (buffer) => { + // memfs + const module = await WebAssembly.compile(buffer) + const instance = await WebAssembly.instantiate(module, { env }) + this.exports = instance.exports + this.mem = new Memory(this.exports.memory) + this.exports.init() + }) + } + + set hostMem(mem) { + this.hostMem_ = mem + } + + setStdinStr(str) { + this.stdinStr = str + this.stdinStrPos = 0 + } + + addDirectory(path) { + this.mem.check() + this.mem.write(this.exports.GetPathBuf(), path) + this.exports.AddDirectoryNode(path.length) + } + + addFile(path, contents) { + const length = contents instanceof ArrayBuffer ? contents.byteLength : contents.length + this.mem.check() + this.mem.write(this.exports.GetPathBuf(), path) + const inode = this.exports.AddFileNode(path.length, length) + const addr = this.exports.GetFileNodeAddress(inode) + this.mem.check() + this.mem.write(addr, contents) + } + + getFileContents(path) { + this.mem.check() + this.mem.write(this.exports.GetPathBuf(), path) + const inode = this.exports.FindNode(path.length) + const addr = this.exports.GetFileNodeAddress(inode) + const size = this.exports.GetFileNodeSize(inode) + return new Uint8Array(this.mem.buffer, addr, size) + } + + abort() { + throw new AbortError() + } + + async host_write(fd, iovs, iovs_len, nwritten_out) { + this.hostMem_.check() + assert(fd <= 2) + let size = 0 + let str = '' + for (let i = 0; i < iovs_len; ++i) { + const buf = this.hostMem_.read32(iovs) + iovs += 4 + const len = this.hostMem_.read32(iovs) + iovs += 4 + str += this.hostMem_.readStr(buf, len) + size += len + } + this.hostMem_.write32(nwritten_out, size) + this.hostWrite(str) + return ESUCCESS + } + + host_read(fd, iovs, iovs_len, nread) { + this.stdinStr = this.hostRead(); + this.hostMem_.check() + assert(fd === 0) + let size = 0 + for (let i = 0; i < iovs_len; ++i) { + const buf = this.hostMem_.read32(iovs) + iovs += 4 + const len = this.hostMem_.read32(iovs) + iovs += 4 + const lenToWrite = Math.min(len, this.stdinStr.length - this.stdinStrPos) + if (lenToWrite === 0) { + break + } + this.hostMem_.write(buf, this.stdinStr.substr(this.stdinStrPos, lenToWrite)) + size += lenToWrite + this.stdinStrPos += lenToWrite + if (lenToWrite !== len) { + break + } + } + this.hostMem_.write32(nread, size) + return ESUCCESS + } + + memfs_log(buf, len) { + this.mem.check() + console.log(this.mem.readStr(buf, len)) + } + + copy_out(clang_dst, memfs_src, size) { + this.hostMem_.check() + const dst = new Uint8Array(this.hostMem_.buffer, clang_dst, size) + this.mem.check() + const src = new Uint8Array(this.mem.buffer, memfs_src, size) + dst.set(src) + } + + copy_in(memfs_dst, clang_src, size) { + this.mem.check() + const dst = new Uint8Array(this.mem.buffer, memfs_dst, size) + this.hostMem_.check() + const src = new Uint8Array(this.hostMem_.buffer, clang_src, size) + dst.set(src) + } +} diff --git a/src/app/notebook/cellTypes/languages/cpp/memory.js b/src/app/notebook/cellTypes/languages/cpp/memory.js new file mode 100644 index 0000000..4e04204 --- /dev/null +++ b/src/app/notebook/cellTypes/languages/cpp/memory.js @@ -0,0 +1,61 @@ +import { readStr } from './shared' + +export class Memory { + constructor(memory) { + this.memory = memory + this.buffer = this.memory.buffer + this.u8 = new Uint8Array(this.buffer) + this.u32 = new Uint32Array(this.buffer) + } + + check() { + if (this.buffer.byteLength === 0) { + this.buffer = this.memory.buffer + this.u8 = new Uint8Array(this.buffer) + this.u32 = new Uint32Array(this.buffer) + } + } + + read8(o) { + return this.u8[o] + } + read32(o) { + return this.u32[o >> 2] + } + write8(o, v) { + this.u8[o] = v + } + write32(o, v) { + this.u32[o >> 2] = v + } + write64(o, vlo, vhi = 0) { + this.write32(o, vlo) + this.write32(o + 4, vhi) + } + + readStr(o, len) { + return readStr(this.u8, o, len) + } + + // Null-terminated string. + writeStr(o, str) { + o += this.write(o, str) + this.write8(o, 0) + return str.length + 1 + } + + write(o, buf) { + if (buf instanceof ArrayBuffer) { + return this.write(o, new Uint8Array(buf)) + } else if (typeof buf === 'string') { + return this.write( + o, + buf.split('').map((x) => x.charCodeAt(0)) + ) + } else { + const dst = new Uint8Array(this.buffer, o, buf.length) + dst.set(buf) + return buf.length + } + } +} diff --git a/src/app/notebook/cellTypes/languages/cpp/shared.js b/src/app/notebook/cellTypes/languages/cpp/shared.js new file mode 100644 index 0000000..b842796 --- /dev/null +++ b/src/app/notebook/cellTypes/languages/cpp/shared.js @@ -0,0 +1,39 @@ +import { AssertError } from './errors.js' + +export function sleep(ms) { + return new Promise((resolve, _) => setTimeout(resolve, ms)) +} + +export function readStr(u8, o, len = -1) { + let str = '' + let end = u8.length + if (len != -1) end = o + len + for (let i = o; i < end && u8[i] != 0; ++i) str += String.fromCharCode(u8[i]) + return str +} + +export function assert(cond) { + if (!cond) { + throw new AssertError('assertion failed.') + } +} + +export function getInstance(module, imports) { + return WebAssembly.instantiate(module, imports) +} + +export function getImportObject(obj, names = []) { + const result = {} + for (let name of names) { + result[name] = obj[name].bind(obj) + } + return result +} + +export function msToSec(start, end) { + return ((end - start) / 1000).toFixed(2) +} + +export const ESUCCESS = 0 + +export const RAF_PROC_EXIT_CODE = 0xc0c0a diff --git a/src/app/notebook/cellTypes/languages/cpp/tar.js b/src/app/notebook/cellTypes/languages/cpp/tar.js new file mode 100644 index 0000000..b23ff57 --- /dev/null +++ b/src/app/notebook/cellTypes/languages/cpp/tar.js @@ -0,0 +1,77 @@ +import { assert, readStr } from './shared' + +export class Tar { + constructor(buffer) { + this.u8 = new Uint8Array(buffer) + this.offset = 0 + } + + readStr(len) { + const result = readStr(this.u8, this.offset, len) + this.offset += len + return result + } + + readOctal(len) { + return parseInt(this.readStr(len), 8) + } + + alignUp() { + this.offset = (this.offset + 511) & ~511 + } + + readEntry() { + if (this.offset + 512 > this.u8.length) { + return null + } + + const entry = { + filename: this.readStr(100), + mode: this.readOctal(8), + owner: this.readOctal(8), + group: this.readOctal(8), + size: this.readOctal(12), + mtim: this.readOctal(12), + checksum: this.readOctal(8), + type: this.readStr(1), + linkname: this.readStr(100), + } + + if (this.readStr(8) !== 'ustar ') { + return null + } + + entry.ownerName = this.readStr(32) + entry.groupName = this.readStr(32) + entry.devMajor = this.readStr(8) + entry.devMinor = this.readStr(8) + entry.filenamePrefix = this.readStr(155) + this.alignUp() + + if (entry.type === '0') { + // Regular file. + entry.contents = this.u8.subarray(this.offset, this.offset + entry.size) + this.offset += entry.size + this.alignUp() + } else if (entry.type !== '5') { + // Directory. + console.log('type', entry.type) + assert(false) + } + return entry + } + + untar(memfs) { + let entry + while ((entry = this.readEntry())) { + switch (entry.type) { + case '0': // Regular file. + memfs.addFile(entry.filename, entry.contents) + break + case '5': + memfs.addDirectory(entry.filename) + break + } + } + } +} diff --git a/src/app/notebook/cellTypes/languages/cpp/worker.ts b/src/app/notebook/cellTypes/languages/cpp/worker.ts new file mode 100644 index 0000000..0100f36 --- /dev/null +++ b/src/app/notebook/cellTypes/languages/cpp/worker.ts @@ -0,0 +1,33 @@ +import { API } from './api' + +let api: API +let port: MessagePort + +const apiOptions = { + hostWrite(s: string) { + port.postMessage({ id: 'write', data: s }) + }, +} + +let currentApp = null + +const onAnyMessage = async (event) => { + switch (event.data.id) { + case 'constructor': + port = event.data.data + port.onmessage = onAnyMessage + api = new API(apiOptions) + break + + case 'setShowTiming': + api.showTiming = event.data.data + break + + case 'compileLinkRun': + currentApp = await api.compileLinkRun(event.data.data) + console.log(`finished compileLinkRun. currentApp = ${currentApp}.`) + break + } +} + +self.addEventListener('message', onAnyMessage) diff --git a/src/assets/clang/clang.wasm b/src/assets/clang/clang.wasm new file mode 100644 index 0000000..3c0910b Binary files /dev/null and b/src/assets/clang/clang.wasm differ diff --git a/src/assets/clang/lld.wasm b/src/assets/clang/lld.wasm new file mode 100644 index 0000000..c1f2906 Binary files /dev/null and b/src/assets/clang/lld.wasm differ diff --git a/src/assets/clang/memfs.wasm b/src/assets/clang/memfs.wasm new file mode 100644 index 0000000..2145523 Binary files /dev/null and b/src/assets/clang/memfs.wasm differ diff --git a/src/assets/clang/sysroot.tar b/src/assets/clang/sysroot.tar new file mode 100644 index 0000000..29235cd Binary files /dev/null and b/src/assets/clang/sysroot.tar differ diff --git a/tsconfig.json b/tsconfig.json index c15138c..78fd007 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, + "allowJs": true, "target": "ES2022", "module": "ES2022", "useDefineForClassFields": false, diff --git a/yarn.lock b/yarn.lock index b9d8f89..2b743f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1636,6 +1636,14 @@ "@codemirror/view" "^6.0.0" "@lezer/common" "^1.0.0" +"@codemirror/lang-cpp@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz#076c98340c3beabde016d7d83e08eebe17254ef9" + integrity sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg== + dependencies: + "@codemirror/language" "^6.0.0" + "@lezer/cpp" "^1.0.0" + "@codemirror/lang-css@^6.0.0": version "6.2.0" resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-6.2.0.tgz#f84f9da392099432445c75e32fdac63ae572315f" @@ -2956,6 +2964,14 @@ resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.1.0.tgz#2e5bfe01d7a2ada6056d93c677bba4f1495e098a" integrity sha512-XPIN3cYDXsoJI/oDWoR2tD++juVrhgIago9xyKhZ7IhGlzdDM9QgC8D8saKNCz5pindGcznFr2HBSsEQSWnSjw== +"@lezer/cpp@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@lezer/cpp/-/cpp-1.1.1.tgz#ac0261f48dc3651bfea13fdaeff35f04c9011a7f" + integrity sha512-eS1M3L3U2mDowoFVPG7tEp01SWu9/68Nx3HEBgLJVn3N9ku7g5S7WdFv0jzmcTipAyONYfZJ+7x4WRkfdB2Ung== + dependencies: + "@lezer/highlight" "^1.0.0" + "@lezer/lr" "^1.0.0" + "@lezer/css@^1.0.0", "@lezer/css@^1.1.0": version "1.1.3" resolved "https://registry.yarnpkg.com/@lezer/css/-/css-1.1.3.tgz#605495b00fd8a122088becf196a93744cbe817fc"