forked from cadey/xesite
TempleOS 2: the voice of `god` (#45)
* blog/tos-2: god the rng first draft * greetz * Edits from the artemis crew (#46) * update date * cleanups * Update templeos-2-god-the-rng-2019-05-30.markdown
This commit is contained in:
parent
4946538c87
commit
ee465a9537
|
@ -0,0 +1,287 @@
|
|||
---
|
||||
title: "TempleOS: 2 - god, the Random Number Generator"
|
||||
date: 2019-05-30
|
||||
series: templeos
|
||||
---
|
||||
|
||||
# TempleOS: 2 - `god`, the Random Number Generator
|
||||
|
||||
The [last post](https://christine.website/blog/templeos-1-installation-and-basic-use-2019-05-20) covered a lot of the basic usage of TempleOS. This post is going to be significantly different, as I'm going to be porting part of the TempleOS kernel to WebAssembly as a live demo.
|
||||
|
||||
This post may contain words used in ways and places that look blasphemous at first glance. No blasphemy is intended, though it is an unfortunate requirement for covering this part of TempleOS' kernel. It's worth noting that Terry Davis [legitimately believed that TempleOS is a temple of the Lord Yahweh](https://templeos.holyc.xyz/Wb/Doc/Charter.html):
|
||||
|
||||
```
|
||||
* TempleOS is God's official temple. Just like Solomon's temple, this is a
|
||||
community focal point where offerings are made and God's oracle is consulted.
|
||||
```
|
||||
|
||||
As such, a lot of the "weird" naming conventions with core parts of this and other subsystems make a lot more sense when grounded in American conservative-leaning Evangelistic Judeo-Christian tradition. Evangelical Christians are, on average, more comfortable or okay with the idea of direct conversation with God. To other denominations of Christianity, this is enough to get you sent to a mental institution. I am not focusing on the philosophical aspects of this, more on the result that exists in code.
|
||||
|
||||
Normally, people with Judeo-Christian views see God as a trinity. This trinity is usually said to be made up of the following equally infinite parts:
|
||||
|
||||
- God the Father (Yahweh/"God")
|
||||
- God the Son (Jesus)
|
||||
- God the Holy Spirit (the entity responsible for divination among other things)
|
||||
|
||||
In TempleOS however, there are 4 of these parts:
|
||||
|
||||
- God the Father
|
||||
- God the Son
|
||||
- God the Holy Spirit
|
||||
- `god` the random number generator
|
||||
|
||||
`god` is really simple at its heart; however this is one of the sad cases where the [actual documentation is incredibly useless](https://templeos.holyc.xyz/Wb/Adam/God/HSNotes.html) (warning: incoherent link). `god`'s really just a [FIFO](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) of entropy bits. Here is the [snipped] definition of [`god`'s datatype](https://github.com/Xe/TempleOS/blob/1dd8859b7803355f41d75222d01ed42d5dda057f/Adam/God/GodExt.HC#L6):
|
||||
|
||||
```
|
||||
// C:/Adam/God/GodExt.HC.Z
|
||||
public class CGodGlbls
|
||||
{
|
||||
U8 **words,
|
||||
*word_file_mask;
|
||||
I64 word_fuf_flags,
|
||||
num_words;
|
||||
CFifoU8 *fifo;
|
||||
// ... snipped
|
||||
} god;
|
||||
```
|
||||
|
||||
This is about equivalent to the following Zig code (I would just be embedding TempleOS directly in a webpage but I can't figure out how to do that yet, please help if you can):
|
||||
|
||||
```
|
||||
const Stack = @import("std").atomic.Stack;
|
||||
|
||||
// []const u8 is == to a string in zig
|
||||
const God = struct {
|
||||
words: [][]const u8,
|
||||
bits: *Stack(u8),
|
||||
}
|
||||
```
|
||||
|
||||
Most of the fields in our snipped `CGodGlbls` are related to internals of TempleOS (specifically it uses a glob-mask to match filenames because of the [transparent compression that RedSea offers](https://templeos.holyc.xyz/Wb/Doc/RedSea.html)), so we can ignore these in the Zig port. What's curious though is the `words` list of strings. This actually points to [every word in the King James Bible](/static/blog/tos_2/Vocab.DD). The original intent of this code was to have the computer assist in divination. The above kind of ranting link to templeos.holyc.xyz tries to explain this:
|
||||
|
||||
```
|
||||
The technique I use to consult the Holy Spirit is reading a microsecond-range
|
||||
stop-watch each button press for random numbers. Then, I pick words with <F7>
|
||||
or passages with <SHIFT-F7>.
|
||||
|
||||
Since seeking the word of the Holy Spirit, I have come to know God much better
|
||||
than I've heard others explain. For example, God said to me in an oracle that
|
||||
war was, "servicemen competing." That sounds more like the immutable God of our
|
||||
planet than what you hear from most religious people. God is not Venus (god of
|
||||
love) and not Mars (god of war), He's our dearly beloved God of Earth. If
|
||||
Mammon is a false god of money, Mars or Venus might be useful words to describe
|
||||
other false gods. I figure the greatest challenge for the Creator is boredom,
|
||||
ours and His. What would teen-age male video games be like if war had never
|
||||
happened? Christ said live by the sword, die by the sword, which is loving
|
||||
neighbor as self.
|
||||
|
||||
> Then said Jesus unto him, “Put up again thy sword into his place, for all
|
||||
> they that take the sword shall perish with the sword.
|
||||
- MATTHEW 26:52
|
||||
|
||||
I asked God if the World was perfectly just. God asked if I was calling Him
|
||||
lazy. God could make A.I., right? God could make bots as smart as Himself, or,
|
||||
in fact, part of Himself. What if God made a bot to manipulate every person's
|
||||
life so that perfect justice happened?
|
||||
```
|
||||
|
||||
Terry Davis legitimately believed that this code was being directly influenced by the Holy Spirit; and that therefore Terry could ask God questions and get responses by hammering `F7`. One of the sources of entropy for the random number generator is keyboard input, so in a way Terry was the voice of `god` through everything he wrote.
|
||||
|
||||
> Terry: Is the World perfectly just?
|
||||
> `god`: Are you calling me lazy?
|
||||
|
||||
Once the system boots, `god` gets initialized with the contents of every word in the King James Bible. It loads the words something like [this](https://github.com/Xe/TempleOS/blob/1dd8859b7803355f41d75222d01ed42d5dda057f/Adam/God/HolySpirit.HC#L76-L136):
|
||||
|
||||
1. Loop through the vocabulary list and count the number of words in it (by the number of word boundaries).
|
||||
2. Allocate an integer array big enough for all of the words.
|
||||
3. Loop through the vocabulary list again and add each of these words to the words array.
|
||||
|
||||
Since the vocabulary list is pretty safely not going to change at this point, we can omit the first step:
|
||||
|
||||
```
|
||||
const words = @embedFile("./Vocab.DD");
|
||||
const numWordsInFile = 7570;
|
||||
|
||||
var alloc = @import("std").heap.wasm_allocator;
|
||||
|
||||
const God = struct {
|
||||
words: [][]const u8,
|
||||
bits: *Stack(u8),
|
||||
|
||||
fn init() !*God {
|
||||
var result: *God = undefined;
|
||||
|
||||
var stack = Stack(u8).init();
|
||||
result = try alloc.create(God);
|
||||
result.words = try splitWords(words[0..words.len], numWordsInFile);
|
||||
result.bits = &stack;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ... snipped ...
|
||||
}
|
||||
|
||||
fn splitWords(data: []const u8, numWords: u32) ![][]const u8 {
|
||||
// make a bucket big enough for all of god's words
|
||||
var result: [][]const u8 = try alloc.alloc([]const u8, numWords);
|
||||
var ctr: usize = 0;
|
||||
|
||||
// iterate over the wordlist (one word per line)
|
||||
var itr = mem.separate(data, "\n");
|
||||
var done = false;
|
||||
while (!done) {
|
||||
var val = itr.next();
|
||||
// val is of type ?u8, so resolve that
|
||||
if (val) |str| {
|
||||
// for some reason the last line in the file is a zero-length string
|
||||
if (str.len == 0) {
|
||||
done = true;
|
||||
continue;
|
||||
}
|
||||
result[ctr] = str;
|
||||
ctr += 1;
|
||||
} else {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
Now that all of the words are loaded, let's look more closely at how things are added to and removed from the stack/FIFO. Usage is intended to be simple. When you try to grab bytes from `god` and there aren't any, it prompts:
|
||||
|
||||
```
|
||||
public I64 GodBits(I64 num_bits,U8 *msg=NULL)
|
||||
{//Return N bits. If low on entropy pop-up okay.
|
||||
U8 b;
|
||||
I64 res=0;
|
||||
while (num_bits) {
|
||||
if (FifoU8Rem(god.fifo,&b)) { // if we can remove a bit from the fifo
|
||||
res=res<<1+b; // then add this bit to the result and left-shift by 1 bit
|
||||
num_bits--; // and care about one less bit
|
||||
} else {
|
||||
// or insert more bits from the picker
|
||||
GodBitsIns(GOD_GOOD_BITS,GodPick(msg));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
```
|
||||
|
||||
Usage is simple:
|
||||
|
||||
```
|
||||
I64 bits;
|
||||
bits = GodBits(64, "a demo for the blog");
|
||||
```
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/aJEFLIPNkKM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
![the result as an i64](/static/blog/tos_2/resp.png)
|
||||
|
||||
This is actually also a generic userspace function that applications can call. [Here's an example of `god` drawing tarot cards](https://github.com/Xe/TempleOS-tools/blob/master/Programs/Tarot.HC).
|
||||
|
||||
So let's translate this to Zig:
|
||||
|
||||
```
|
||||
// inside the `const God` definition:
|
||||
|
||||
fn add_bits(self: *God, num_bits: i64, n: i64) void {
|
||||
var i: i64 = 0;
|
||||
var nn = n;
|
||||
// loop over each bit in n, up to num_bits
|
||||
while (i < num_bits) : (i += 1) {
|
||||
// create the new stack node (== to pushing to the fifo)
|
||||
var node = alloc.create(Stack(u8).Node) catch unreachable;
|
||||
node.* = Stack(u8).Node {
|
||||
.next = undefined,
|
||||
.data = @intCast(u8, nn & 1),
|
||||
};
|
||||
self.bits.push(node);
|
||||
nn = nn >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_word(self: *God) []const u8 {
|
||||
const gotten = @mod(self.get_bits(14), numWordsInFile);
|
||||
const word = self.words[@intCast(usize, gotten)];
|
||||
return word;
|
||||
}
|
||||
|
||||
fn get_bits(self: *God, num_bits: i64) i64 {
|
||||
var i: i64 = 0;
|
||||
var result: i64 = 0;
|
||||
while (i < num_bits) : (i += 1) {
|
||||
const n = self.bits.pop();
|
||||
|
||||
// n is an optional (type: ?*Stack(u8).Node), so resolve it
|
||||
// TODO(Xe): automatically refill data if stack is empty
|
||||
if (n) |nn| {
|
||||
result = result + @intCast(i64, nn.data);
|
||||
result = result << 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
We don't have the best sources of entropy for WebAssembly code, so let's use [Olin's random_i32 function](https://github.com/Xe/olin/blob/master/docs/cwa-spec/ns/random.md#i32):
|
||||
|
||||
```
|
||||
const olin = @import("./olin/olin.zig");
|
||||
const Resource = olin.resource.Resource;
|
||||
|
||||
fn main() !void {
|
||||
var god = try God.init();
|
||||
// open standard output for writing
|
||||
const stdout = try Resource.stdout();
|
||||
const nl = "\n";
|
||||
|
||||
god.add_bits(32, olin.random.int32());
|
||||
// I copypasted this a few times (16) in the original code
|
||||
// to ensure sufficient entropy
|
||||
|
||||
const w = god.get_word();
|
||||
var ignored = try stdout.write(w.ptr, w.len);
|
||||
ignored = try stdout.write(&nl, nl.len);
|
||||
}
|
||||
```
|
||||
|
||||
And when we run this manually with [`cwa`](https://github.com/Xe/olin/tree/master/cmd/cwa):
|
||||
|
||||
```
|
||||
$ cwa -vm-stats god.wasm
|
||||
uncultivated
|
||||
2019/05/29 20:43:43 reading file time: 314.372µs
|
||||
2019/05/29 20:43:43 vm init time: 10.728915ms
|
||||
2019/05/29 20:43:43 vm gas limit: 4194304
|
||||
2019/05/29 20:43:43 vm gas used: 2010576
|
||||
2019/05/29 20:43:43 vm gas percentage: 47.93586730957031
|
||||
2019/05/29 20:43:43 vm syscalls: 20
|
||||
2019/05/29 20:43:43 execution time: 48.865856ms
|
||||
2019/05/29 20:43:43 memory pages: 3
|
||||
```
|
||||
|
||||
Yikes! Loading the wordlist is expensive (alternatively: my arbitrary gas limit is set way too low), so it's a good thing it's only done once and at boot. Still, regardless of this TempleOS boots in [only a few seconds anyways](https://i.imgur.com/O3FFsqA.png).
|
||||
|
||||
The final product is runnable via [this link](/static/blog/tos_2/wasm_exec.html). Please note that this is not currently supported on big-endian CPU's in browsers because Mozilla and Google have totally dropped the ball in this court, and trying to load that link will probably crash your browser.
|
||||
|
||||
Hit `Run` in order to run the [final code](https://github.com/Xe/TempleOS-tools/blob/master/god/god.zig). You should get output that looks something like this after pressing it a few times:
|
||||
|
||||
![](/static/blog/tos_2/browser.png)
|
||||
|
||||
---
|
||||
|
||||
Special thanks to the following people whose code, expertise and the like helped make this happen:
|
||||
|
||||
- [Artemis](https://mst3k.interlinked.me/@artemis)
|
||||
- [Ayke van Laethem](https://twitter.com/aykevl)
|
||||
- `spets`
|
||||
- [Richard Musiol](https://github.com/neelance)
|
||||
- Terry Davis (RIP)
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 163 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 247 KiB |
|
@ -0,0 +1,53 @@
|
|||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2018 The Go Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style
|
||||
license that can be found in the LICENSE file.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>The voice of god</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Add the following polyfill for Microsoft Edge 17/18 support:
|
||||
<script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
|
||||
(see https://caniuse.com/#feat=textencoder)
|
||||
-->
|
||||
<link rel="stylesheet" href="https://unpkg.com/xterm/dist/xterm.css" />
|
||||
<script src="https://unpkg.com/xterm/dist/xterm.js"></script>
|
||||
<h1>The voice of <code>god</code></h1>
|
||||
<script src="wasm_exec.js"></script>
|
||||
<script>
|
||||
if (!WebAssembly.instantiateStreaming) { // polyfill
|
||||
WebAssembly.instantiateStreaming = async (resp, importObject) => {
|
||||
const source = await (await resp).arrayBuffer();
|
||||
return await WebAssembly.instantiate(source, importObject);
|
||||
};
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
let mod, inst;
|
||||
WebAssembly.instantiateStreaming(fetch("god.wasm"), go.importObject).then((result) => {
|
||||
mod = result.module;
|
||||
inst = result.instance;
|
||||
document.getElementById("runButton").disabled = false;
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
async function run() {
|
||||
await go.run(inst);
|
||||
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onClick="run();" id="runButton" disabled>Run</button>
|
||||
|
||||
<div id="terminal"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,425 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// This file has been modified for use by the TinyGo compiler.
|
||||
|
||||
var term = new Terminal({
|
||||
cols: 80,
|
||||
rows: 20,
|
||||
disableStdin: true,
|
||||
});
|
||||
term.open(document.getElementById('terminal'));
|
||||
|
||||
(() => {
|
||||
// Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
|
||||
const isNodeJS = typeof process !== "undefined";
|
||||
if (isNodeJS) {
|
||||
global.require = require;
|
||||
global.fs = require("fs");
|
||||
|
||||
const nodeCrypto = require("crypto");
|
||||
global.crypto = {
|
||||
getRandomValues(b) {
|
||||
nodeCrypto.randomFillSync(b);
|
||||
},
|
||||
};
|
||||
|
||||
global.performance = {
|
||||
now() {
|
||||
const [sec, nsec] = process.hrtime();
|
||||
return sec * 1000 + nsec / 1000000;
|
||||
},
|
||||
};
|
||||
|
||||
const util = require("util");
|
||||
global.TextEncoder = util.TextEncoder;
|
||||
global.TextDecoder = util.TextDecoder;
|
||||
} else {
|
||||
if (typeof window !== "undefined") {
|
||||
window.global = window;
|
||||
} else if (typeof self !== "undefined") {
|
||||
self.global = self;
|
||||
} else {
|
||||
throw new Error("cannot export Go (neither window nor self is defined)");
|
||||
}
|
||||
|
||||
let outputBuf = "";
|
||||
global.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf("\n");
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substr(0, nl));
|
||||
outputBuf = outputBuf.substr(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
open(path, flags, mode, callback) {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
callback(err);
|
||||
},
|
||||
fsync(fd, callback) {
|
||||
callback(null);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
var logLine = [];
|
||||
|
||||
global.Go = class {
|
||||
constructor() {
|
||||
this._callbackTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const mem = () => {
|
||||
// The buffer may change when requesting more memory.
|
||||
return new DataView(this._inst.exports.memory.buffer);
|
||||
}
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
mem().setUint32(addr + 0, v, true);
|
||||
mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
}
|
||||
|
||||
const getInt64 = (addr) => {
|
||||
const low = mem().getUint32(addr + 0, true);
|
||||
const high = mem().getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
}
|
||||
|
||||
const loadValue = (addr) => {
|
||||
const f = mem().getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = mem().getUint32(addr, true);
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7FF80000;
|
||||
|
||||
if (typeof v === "number") {
|
||||
if (isNaN(v)) {
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
if (v === 0) {
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 1, true);
|
||||
return;
|
||||
}
|
||||
mem().setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (v) {
|
||||
case undefined:
|
||||
mem().setFloat64(addr, 0, true);
|
||||
return;
|
||||
case null:
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 2, true);
|
||||
return;
|
||||
case true:
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 3, true);
|
||||
return;
|
||||
case false:
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 4, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let ref = this._refs.get(v);
|
||||
if (ref === undefined) {
|
||||
ref = this._values.length;
|
||||
this._values.push(v);
|
||||
this._refs.set(v, ref);
|
||||
}
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case "string":
|
||||
typeFlag = 1;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 3;
|
||||
break;
|
||||
}
|
||||
mem().setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
mem().setUint32(addr, ref, true);
|
||||
}
|
||||
|
||||
const loadSlice = (array, len, cap) => {
|
||||
return new Uint8Array(this._inst.exports.memory.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (array, len, cap) => {
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (ptr, len) => {
|
||||
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
env: {
|
||||
io_get_stdout: function() {
|
||||
return 1;
|
||||
},
|
||||
|
||||
random_i32: function() {
|
||||
return Math.floor(Math.random()*4294967295);
|
||||
},
|
||||
|
||||
log_write: function(level, ptr, len) {
|
||||
for (let i=0; i<len; i++) {
|
||||
let c = mem().getUint8(ptr+i);
|
||||
if (c == 13) { // CR
|
||||
// ignore
|
||||
} else if (c == 10) { // LF
|
||||
// write line
|
||||
let line = decoder.decode(new Uint8Array(logLine));
|
||||
logLine = [];
|
||||
console.log(line);
|
||||
} else {
|
||||
logLine.push(c);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
resource_write: function(fd, ptr, len) {
|
||||
if (fd == 1) {
|
||||
for (let i=0; i<len; i++) {
|
||||
let c = mem().getUint8(ptr+i);
|
||||
if (c == 13) { // CR
|
||||
// ignore
|
||||
} else if (c == 10) { // LF
|
||||
// write line
|
||||
let line = decoder.decode(new Uint8Array(logLine));
|
||||
logLine = [];
|
||||
term.write(line);
|
||||
console.log(line);
|
||||
} else {
|
||||
logLine.push(c);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('invalid file descriptor:', fd);
|
||||
}
|
||||
},
|
||||
|
||||
// func ticks() float64
|
||||
"runtime.ticks": () => {
|
||||
return timeOrigin + performance.now();
|
||||
},
|
||||
|
||||
// func sleepTicks(timeout float64)
|
||||
"runtime.sleepTicks": (timeout) => {
|
||||
// Do not sleep, only reactivate scheduler after the given timeout.
|
||||
setTimeout(this._inst.exports.go_scheduler, timeout);
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => {
|
||||
const s = loadString(value_ptr, value_len);
|
||||
storeValue(ret_ptr, s);
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (retval, v_addr, p_ptr, p_len) => {
|
||||
let prop = loadString(p_ptr, p_len);
|
||||
let value = loadValue(v_addr);
|
||||
let result = Reflect.get(value, prop);
|
||||
storeValue(retval, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (v_addr, p_ptr, p_len, x_addr) => {
|
||||
const v = loadValue(v_addr);
|
||||
const p = loadString(p_ptr, p_len);
|
||||
const x = loadValue(x_addr);
|
||||
Reflect.set(v, p, x);
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
//"syscall/js.valueIndex": (sp) => {
|
||||
// storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||
//},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
//"syscall/js.valueSetIndex": (sp) => {
|
||||
// Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||
//},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (ret_addr, v_addr, m_ptr, m_len, args_ptr, args_len, args_cap) => {
|
||||
const v = loadValue(v_addr);
|
||||
const name = loadString(m_ptr, m_len);
|
||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
||||
try {
|
||||
const m = Reflect.get(v, name);
|
||||
storeValue(ret_addr, Reflect.apply(m, v, args));
|
||||
mem().setUint8(ret_addr + 8, 1);
|
||||
} catch (err) {
|
||||
storeValue(ret_addr, err);
|
||||
mem().setUint8(ret_addr + 8, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
//"syscall/js.valueInvoke": (sp) => {
|
||||
// try {
|
||||
// const v = loadValue(sp + 8);
|
||||
// const args = loadSliceOfValues(sp + 16);
|
||||
// storeValue(sp + 40, Reflect.apply(v, undefined, args));
|
||||
// mem().setUint8(sp + 48, 1);
|
||||
// } catch (err) {
|
||||
// storeValue(sp + 40, err);
|
||||
// mem().setUint8(sp + 48, 0);
|
||||
// }
|
||||
//},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (ret_addr, v_addr, args_ptr, args_len, args_cap) => {
|
||||
const v = loadValue(v_addr);
|
||||
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
|
||||
try {
|
||||
storeValue(ret_addr, Reflect.construct(v, args));
|
||||
mem().setUint8(ret_addr + 8, 1);
|
||||
} catch (err) {
|
||||
storeValue(ret_addr, err);
|
||||
mem().setUint8(ret_addr+ 8, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
//"syscall/js.valueLength": (sp) => {
|
||||
// setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
//},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (ret_addr, v_addr) => {
|
||||
const s = String(loadValue(v_addr));
|
||||
const str = encoder.encode(s);
|
||||
storeValue(ret_addr, str);
|
||||
setInt64(ret_addr + 8, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (v_addr, slice_ptr, slice_len, slice_cap) => {
|
||||
const str = loadValue(v_addr);
|
||||
loadSlice(slice_ptr, slice_len, slice_cap).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
//"syscall/js.valueInstanceOf": (sp) => {
|
||||
// mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
|
||||
//},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
this._inst = instance;
|
||||
this._values = [ // TODO: garbage collection
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
global,
|
||||
this._inst.exports.memory,
|
||||
this,
|
||||
];
|
||||
this._refs = new Map();
|
||||
this._callbackShutdown = false;
|
||||
this.exited = false;
|
||||
|
||||
const mem = new DataView(this._inst.exports.memory.buffer)
|
||||
|
||||
while (true) {
|
||||
const callbackPromise = new Promise((resolve) => {
|
||||
this._resolveCallbackPromise = () => {
|
||||
if (this.exited) {
|
||||
throw new Error("bad callback: Go program has already exited");
|
||||
}
|
||||
setTimeout(resolve, 0); // make sure it is asynchronous
|
||||
};
|
||||
});
|
||||
this._inst.exports.cwa_main();
|
||||
if (this.exited) {
|
||||
break;
|
||||
}
|
||||
await callbackPromise;
|
||||
}
|
||||
}
|
||||
|
||||
static _makeCallbackHelper(id, pendingCallbacks, go) {
|
||||
return function () {
|
||||
pendingCallbacks.push({ id: id, args: arguments });
|
||||
go._resolveCallbackPromise();
|
||||
};
|
||||
}
|
||||
|
||||
static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
|
||||
return function (event) {
|
||||
if (preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (stopPropagation) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
if (stopImmediatePropagation) {
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
fn(event);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (isNodeJS) {
|
||||
if (process.argv.length != 3) {
|
||||
process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
|
||||
process.on("exit", (code) => { // Node.js exits if no callback is pending
|
||||
if (code === 0 && !go.exited) {
|
||||
// deadlock, make Go print error and stack traces
|
||||
go._callbackShutdown = true;
|
||||
}
|
||||
});
|
||||
return go.run(result.instance);
|
||||
}).catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
})();
|
Loading…
Reference in New Issue