runtime_wasm.mx raw
1 //go:build js && wasm
2
3 package runtime
4
5 import "unsafe"
6
7 // putchar writes a single byte to stdout via the JS console.
8 //
9 //go:wasm-module wasi_snapshot_preview1
10 //export fd_write
11 func fd_write(fd int32, iovs unsafe.Pointer, iovs_len int32, nwritten unsafe.Pointer) int32
12
13 func putchar(c byte) {
14 buf := [1]byte{c}
15 iov := [2]uintptr{uintptr(unsafe.Pointer(&buf[0])), 1}
16 var nw int32
17 fd_write(1, unsafe.Pointer(&iov[0]), 1, unsafe.Pointer(&nw))
18 }
19
20 // abort terminates the program.
21 //
22 //export abort
23 func abort() {
24 trap()
25 }
26
27 // hardwareRand returns a random uint64. On WASM/JS, we use a simple
28 // xorshift fallback since there's no hardware RNG instruction.
29 var randState uint64 = 0x853c49e6748fea9b
30
31 func hardwareRand() (n uint64, ok bool) {
32 // xorshift64*
33 randState ^= randState >> 12
34 randState ^= randState << 25
35 randState ^= randState >> 27
36 return randState * 0x2545F4914F6CDD1D, true
37 }
38
39 // Heap management for WASM linear memory.
40 var heapStart, heapEnd uintptr
41
42 // wasm_memory_size returns the current memory size in pages (64KB each).
43 //
44 //go:linkname wasm_memory_size llvm.wasm.memory.size.i32
45 func wasm_memory_size(index int32) int32
46
47 // wasm_memory_grow grows memory by delta pages. Returns previous size or -1.
48 //
49 //go:linkname wasm_memory_grow llvm.wasm.memory.grow.i32
50 func wasm_memory_grow(index int32, delta int32) int32
51
52 const wasmPageSize = 64 * 1024
53
54 // allocateHeap initializes the WASM heap from linear memory.
55 func allocateHeap() {
56 pages := wasm_memory_size(0)
57 heapStart = uintptr(pages) * wasmPageSize
58 // Start with 8 pages (512KB)
59 if wasm_memory_grow(0, 8) == -1 {
60 runtimePanic("cannot allocate heap memory")
61 }
62 heapEnd = heapStart + 8*wasmPageSize
63 }
64
65 func growHeap() bool {
66 oldPages := wasm_memory_grow(0, 8)
67 if oldPages == -1 {
68 return false
69 }
70 heapEnd = uintptr(oldPages+8) * wasmPageSize
71 return true
72 }
73
74 // globalsStart/globalsEnd are linker-provided symbols for GC scanning.
75 //
76 //go:extern __data_start
77 var globalsStart [0]byte
78
79 //go:extern __data_end
80 var globalsEnd [0]byte
81
82 // Timing functions for WASM.
83 var wasmTickOffset int64
84
85 func ticks() timeUnit {
86 return timeUnit(monotime())
87 }
88
89 //go:wasm-module wasi_snapshot_preview1
90 //export clock_time_get
91 func clock_time_get(clockID int32, precision int64, time unsafe.Pointer) int32
92
93 func monotime() int64 {
94 var t int64
95 clock_time_get(1, 0, unsafe.Pointer(&t)) // CLOCK_MONOTONIC = 1
96 return t
97 }
98
99 func nanosecondsToTicks(ns int64) timeUnit {
100 return timeUnit(ns)
101 }
102
103 func ticksToNanoseconds(t timeUnit) int64 {
104 return int64(t)
105 }
106
107 func sleepTicks(d timeUnit) {
108 // No-op on WASM — cooperative scheduling handles yields.
109 }
110
111 func now() (int64, int32, int64) {
112 var t int64
113 clock_time_get(0, 0, unsafe.Pointer(&t)) // CLOCK_REALTIME = 0
114 sec := t / 1000000000
115 nsec := int32(t % 1000000000)
116 mono := monotime()
117 return sec, nsec, mono
118 }
119
120 // libc_errno_location is not available on WASM.
121 func libc_errno_location() *int32 {
122 return &wasmErrno
123 }
124
125 var wasmErrno int32
126
127 // savedStackPointer is used by the GC to save the stack pointer.
128 var savedStackPointer uintptr
129
130 // _start is the WASI entry point for WASM executables.
131 // WASM is single-threaded, so we bypass the scheduler and call main directly.
132 //
133 //export _start
134 func _start() {
135 // Initialize heap from linear memory.
136 allocateHeap()
137 initRand()
138 initHeap()
139 initAll()
140 callMain()
141 }
142