9 Commits

Author SHA1 Message Date
alu 13b522f6a0 fix missing dist dir make issue 2026-07-06 20:40:48 +00:00
alu 05cd2d79df remove minification of wasm.js 2026-07-06 20:36:42 +00:00
alu bc52a250a4 update go mod 2026-07-06 18:44:32 +00:00
= 830c0a7586 update wfa.js,
reduce clobbering of global namespaces
2025-10-02 21:56:39 +00:00
= c94a7902c0 remove imports,
switch to opt=s,
add debug index.html
2025-09-30 17:41:14 +00:00
alu 8ccb45748d update go mod 2025-09-17 06:13:27 +00:00
alu 63a375995c minor code cleanup, use uint64 for wavefront values 2025-05-07 21:20:13 +00:00
alu 2351faf2d7 cleanup code 2025-05-01 18:04:42 +00:00
alu a446bbd923 fix readme,
update go mod
2025-03-12 00:09:18 +00:00
11 changed files with 202 additions and 158 deletions
+9 -4
View File
@@ -1,9 +1,10 @@
.PHONY: build clean test .PHONY: build clean test dev-init
build: clean build: clean
@echo "======================== Building Binary =======================" @echo "======================== Building Binary ======================="
minify wfa.js > dist/wfa.js mkdir -p dist
GOOS=js GOARCH=wasm CGO_ENABLED=0 tinygo build -panic=trap -no-debug -opt=2 -target=wasm -o dist/wfa.wasm . cp wfa.js dist/wfa.js
GOOS=js GOARCH=wasm CGO_ENABLED=0 tinygo build -panic=trap -no-debug -opt=s -target=wasm -o dist/wfa.wasm .
clean: clean:
@echo "======================== Cleaning Project ======================" @echo "======================== Cleaning Project ======================"
@@ -23,4 +24,8 @@ test:
go tool pprof -top mem.prof go tool pprof -top mem.prof
@rm -f mem.prof @rm -f mem.prof
@rm -f test.test @rm -f test.test
dev-init:
apt install minify
go get -t wfa/test
+1 -1
View File
@@ -1,6 +1,6 @@
# Using WFA-JS # Using WFA-JS
Download `wfa.js` and `wfa.wasm`from [releases](https://git.tronnet.net/tronnet/WFA-JS/releases) to your project. Add to your script: Download `wfa.js` and `wfa.wasm`from [releases](https://git.tronnet.net/alu/WFA-JS/releases) to your project. Add to your script:
``` ```
import wfa from "./wfa.js" import wfa from "./wfa.js"
+5 -5
View File
@@ -1,15 +1,15 @@
module wfa module wfa
go 1.23.2 go 1.26.4
require ( require (
github.com/schollz/progressbar/v3 v3.17.1 github.com/schollz/progressbar/v3 v3.19.1
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f golang.org/x/exp v0.0.0-20260611194520-c48552f49976
) )
require ( require (
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.27.0 // indirect golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.26.0 // indirect golang.org/x/term v0.44.0 // indirect
) )
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script src="wfa.js" type="module"></script>
<script type="module">
import wfaInit from "./wfa.js";
wfaInit("dist/wfa.wasm");
window.addEventListener("DOMContentLoaded", () => {
document.querySelector("#submit").addEventListener("click", () => {
a = document.querySelector("#a").value
b = document.querySelector("#b").value
const penalties = {
m: 0,
x: 1,
o: 0,
e: 1
};
const { score, CIGAR } = global.wfa.wfAlign(a, b, penalties, true);
const alignment = global.wfa.DecodeCIGAR(CIGAR);
document.querySelector("#result").innerText = `${score}, ${CIGAR}, ${alignment}`;
})
});
</script>
</head>
<body>
<label>A: </label><input id="a">
<label>B: </label><input id="b">
<button id="submit">Submit</button>
<p><span>Result: </span><span id="result"></span></p>
</body>
</html>
+2 -2
View File
@@ -7,8 +7,8 @@ import (
func main() { func main() {
c := make(chan bool) c := make(chan bool)
js.Global().Set("wfAlign", js.FuncOf(wfAlign)) js.Global().Get("wfa").Set("wfAlign", js.FuncOf(wfAlign))
js.Global().Set("DecodeCIGAR", js.FuncOf(DecodeCIGAR)) js.Global().Get("wfa").Set("DecodeCIGAR", js.FuncOf(DecodeCIGAR))
<-c <-c
} }
+10 -17
View File
@@ -6,38 +6,31 @@ type PositiveSlice[T any] struct {
defaultValue T defaultValue T
} }
func (a *PositiveSlice[T]) TranslateIndex(idx int) int {
return idx
}
func (a *PositiveSlice[T]) Valid(idx int) bool { func (a *PositiveSlice[T]) Valid(idx int) bool {
actualIdx := a.TranslateIndex(idx) return 0 <= idx && idx < len(a.valid) && a.valid[idx]
return 0 <= actualIdx && actualIdx < len(a.valid) && a.valid[actualIdx]
} }
func (a *PositiveSlice[T]) Get(idx int) T { func (a *PositiveSlice[T]) Get(idx int) T {
actualIdx := a.TranslateIndex(idx) if 0 <= idx && idx < len(a.valid) && a.valid[idx] { // idx is in the slice
if 0 <= actualIdx && actualIdx < len(a.valid) && a.valid[actualIdx] { // idx is in the slice return a.data[idx]
return a.data[actualIdx]
} else { // idx is out of the slice } else { // idx is out of the slice
return a.defaultValue return a.defaultValue
} }
} }
func (a *PositiveSlice[T]) Set(idx int, value T) { func (a *PositiveSlice[T]) Set(idx int, value T) {
actualIdx := a.TranslateIndex(idx) if idx >= len(a.valid) { // idx is outside the slice
if actualIdx < 0 || actualIdx >= len(a.valid) { // idx is outside the slice // expand data array to 2*idx
// expand data array to actualIdx newData := make([]T, 2*idx+1)
newData := make([]T, 2*actualIdx+1)
copy(newData, a.data) copy(newData, a.data)
a.data = newData a.data = newData
// expand valid array to actualIdx // expand valid array to 2*idx
newValid := make([]bool, 2*actualIdx+1) newValid := make([]bool, 2*idx+1)
copy(newValid, a.valid) copy(newValid, a.valid)
a.valid = newValid a.valid = newValid
} }
a.data[actualIdx] = value a.data[idx] = value
a.valid[actualIdx] = true a.valid[idx] = true
} }
+17 -19
View File
@@ -1,5 +1,9 @@
package wfa package wfa
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Result struct { type Result struct {
Score int Score int
CIGAR string CIGAR string
@@ -40,25 +44,22 @@ func UnpackWavefrontLoHi(lohi WavefrontLoHi) (int, int) {
return loBM, hiBM return loBM, hiBM
} }
// bitpacked wavefront values with 1 valid bit, 3 traceback bits, and 28 bits for the diag distance // bitpacked wavefront values with 1 valid bit, 3 traceback bits, and 60 bits for the diag distance
// technically this restricts to alignments with less than 268 million characters but that should be sufficient for most cases type WavefrontValue uint64
type WavefrontValue uint32
// TODO: add 64 bit packed value in case more than 268 million characters are needed
// PackWavefrontValue: packs a diag value and traceback into a WavefrontValue // PackWavefrontValue: packs a diag value and traceback into a WavefrontValue
func PackWavefrontValue(value uint32, traceback Traceback) WavefrontValue { func PackWavefrontValue(value uint64, traceback Traceback) WavefrontValue {
validBM := uint32(0x8000_0000) validBM := uint64(0x8000_0000_0000_0000)
tracebackBM := uint32(traceback&0x0000_0007) << 28 tracebackBM := uint64(traceback&0x0000_0007) << 60
valueBM := value & 0x0FFF_FFFF valueBM := uint64(value) & 0x0FFF_FFFF_FFFF_FFFF
return WavefrontValue(validBM | tracebackBM | valueBM) return WavefrontValue(validBM | tracebackBM | valueBM)
} }
// UnpackWavefrontValue: opens a WavefrontValue into a valid bool, diag value and traceback // UnpackWavefrontValue: opens a WavefrontValue into a valid bool, diag value and traceback
func UnpackWavefrontValue(wfv WavefrontValue) (bool, uint32, Traceback) { func UnpackWavefrontValue(wfv WavefrontValue) (bool, uint64, Traceback) {
validBM := wfv&0x8000_0000 != 0 validBM := wfv&0x8000_0000_0000_0000 != 0
tracebackBM := uint8(wfv & 0x7000_0000 >> 28) tracebackBM := uint8(wfv & 0x7000_0000_0000_0000 >> 60)
valueBM := uint32(wfv & 0x0FFF_FFFF) valueBM := uint64(wfv & 0x0000_0000_FFFF_FFFF)
return validBM, valueBM, Traceback(tracebackBM) return validBM, valueBM, Traceback(tracebackBM)
} }
@@ -71,12 +72,9 @@ type Wavefront struct { // since wavefronts store diag distance, they should nev
// NewWavefront: returns a new wavefront with size accomodating lo and hi (inclusive) // NewWavefront: returns a new wavefront with size accomodating lo and hi (inclusive)
func NewWavefront(lo int, hi int) *Wavefront { func NewWavefront(lo int, hi int) *Wavefront {
a := &Wavefront{} a := &Wavefront{}
a.lohi = PackWavefrontLoHi(lo, hi) a.lohi = PackWavefrontLoHi(lo, hi)
size := hi - lo size := hi - lo
a.data = make([]WavefrontValue, size+1)
newData := make([]WavefrontValue, size+1)
a.data = newData
return a return a
} }
@@ -134,12 +132,12 @@ func NewWavefrontComponent() *WavefrontComponent {
} }
// GetVal: get value for wavefront=score, diag=k => returns ok, value, traceback // GetVal: get value for wavefront=score, diag=k => returns ok, value, traceback
func (w *WavefrontComponent) GetVal(score int, k int) (bool, uint32, Traceback) { func (w *WavefrontComponent) GetVal(score int, k int) (bool, uint64, Traceback) {
return UnpackWavefrontValue(w.W.Get(score).Get(k)) return UnpackWavefrontValue(w.W.Get(score).Get(k))
} }
// SetVal: set value, traceback for wavefront=score, diag=k // SetVal: set value, traceback for wavefront=score, diag=k
func (w *WavefrontComponent) SetVal(score int, k int, val uint32, tb Traceback) { func (w *WavefrontComponent) SetVal(score int, k int, val uint64, tb Traceback) {
w.W.Get(score).Set(k, PackWavefrontValue(val, tb)) w.W.Get(score).Set(k, PackWavefrontValue(val, tb))
} }
+41 -41
View File
@@ -1,23 +1,18 @@
package wfa package wfa
import ( const MaxInt = int(^uint(0) >> 1)
"math" const MinInt = -MaxInt - 1
"strings"
"golang.org/x/exp/constraints"
)
// convert an unsigned into to string
func UIntToString(num uint) string { // num assumed to be positive func UIntToString(num uint) string { // num assumed to be positive
var builder strings.Builder str := []rune{}
for num > 0 { for num > 0 {
digit := num % 10 digit := num % 10
builder.WriteRune(rune('0' + digit)) str = append(str, rune('0'+digit))
num /= 10 num /= 10
} }
// Reverse the string as we built it in reverse order
str := []rune(builder.String())
for i, j := 0, len(str)-1; i < j; i, j = i+1, j-1 { for i, j := 0, len(str)-1; i < j; i, j = i+1, j-1 {
str[i], str[j] = str[j], str[i] str[i], str[j] = str[j], str[i]
} }
@@ -25,8 +20,9 @@ func UIntToString(num uint) string { // num assumed to be positive
return string(str) return string(str)
} }
// decode runlength encoded string such as CIGARs
func RunLengthDecode(encoded string) string { func RunLengthDecode(encoded string) string {
decoded := strings.Builder{} decoded := []rune{}
length := len(encoded) length := len(encoded)
i := 0 i := 0
@@ -42,45 +38,30 @@ func RunLengthDecode(encoded string) string {
if i < length { if i < length {
char := encoded[i] char := encoded[i]
for j := 0; j < runLength; j++ { for j := 0; j < runLength; j++ {
decoded.WriteByte(char) decoded = append(decoded, rune(char))
} }
i++ // Move past the character i++ // Move past the character
} }
} }
return decoded.String() return string(decoded)
} }
func SafeMin[T constraints.Integer](values []T, idx int) T { // given the min index, return the item in values at that index
func SafeMin[T Integer](values []T, idx int) T {
return values[idx] return values[idx]
} }
func SafeMax[T constraints.Integer](values []T, idx int) T { // given the max index, return the item in values at that index
func SafeMax[T Integer](values []T, idx int) T {
return values[idx] return values[idx]
} }
func SafeArgMax[T constraints.Integer](valids []bool, values []T) (bool, int) { // given array of values and corresponding array of valid flags, find the min of value which is valid or return false if there does not exist any
hasValid := false func SafeArgMin[T Integer](valids []bool, values []T) (bool, int) {
maxIndex := 0
maxValue := math.MinInt
for i := 0; i < len(valids); i++ {
if valids[i] && int(values[i]) > maxValue {
hasValid = true
maxIndex = i
maxValue = int(values[i])
}
}
if hasValid {
return true, maxIndex
} else {
return false, 0
}
}
func SafeArgMin[T constraints.Integer](valids []bool, values []T) (bool, int) {
hasValid := false hasValid := false
minIndex := 0 minIndex := 0
minValue := math.MaxInt minValue := MaxInt
for i := 0; i < len(valids); i++ { for i := 0; i < len(valids); i++ {
if valids[i] && int(values[i]) < minValue { if valids[i] && int(values[i]) < minValue {
hasValid = true hasValid = true
@@ -95,6 +76,22 @@ func SafeArgMin[T constraints.Integer](valids []bool, values []T) (bool, int) {
} }
} }
// given array of values and corresponding array of valid flags, find the max of value which is valid or return false if there does not exist any
func SafeArgMax[T Integer](valids []bool, values []T) (bool, int) {
hasValid := false
maxIndex := 0
maxValue := MinInt
for i := range valids {
if valids[i] && int(values[i]) > maxValue {
hasValid = true
maxIndex = i
maxValue = int(values[i])
}
}
return hasValid, maxIndex
}
// set the lext lo and hi bounds for wavefronts M, I, D
func NextLoHi(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponent, score int, penalties Penalty) (int, int) { func NextLoHi(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponent, score int, penalties Penalty) (int, int) {
x := penalties.X x := penalties.X
o := penalties.O o := penalties.O
@@ -125,6 +122,7 @@ func NextLoHi(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponen
return lo, hi return lo, hi
} }
// set the traceback and diag value for the next I wavefront
func NextI(M *WavefrontComponent, I *WavefrontComponent, score int, k int, penalties Penalty) { func NextI(M *WavefrontComponent, I *WavefrontComponent, score int, k int, penalties Penalty) {
o := penalties.O o := penalties.O
e := penalties.E e := penalties.E
@@ -132,13 +130,14 @@ func NextI(M *WavefrontComponent, I *WavefrontComponent, score int, k int, penal
a_ok, a, _ := M.GetVal(score-o-e, k-1) a_ok, a, _ := M.GetVal(score-o-e, k-1)
b_ok, b, _ := I.GetVal(score-e, k-1) b_ok, b, _ := I.GetVal(score-e, k-1)
ok, nextITraceback := SafeArgMax([]bool{a_ok, b_ok}, []uint32{a, b}) ok, nextITraceback := SafeArgMax([]bool{a_ok, b_ok}, []uint64{a, b})
nextIVal := SafeMax([]uint32{a, b}, nextITraceback) + 1 // important that the +1 is here nextIVal := SafeMax([]uint64{a, b}, nextITraceback) + 1 // important that the +1 is here
if ok { if ok {
I.SetVal(score, k, nextIVal, []Traceback{OpenIns, ExtdIns}[nextITraceback]) I.SetVal(score, k, nextIVal, []Traceback{OpenIns, ExtdIns}[nextITraceback])
} }
} }
// set the traceback and diag value for the next D wavefront
func NextD(M *WavefrontComponent, D *WavefrontComponent, score int, k int, penalties Penalty) { func NextD(M *WavefrontComponent, D *WavefrontComponent, score int, k int, penalties Penalty) {
o := penalties.O o := penalties.O
e := penalties.E e := penalties.E
@@ -146,13 +145,14 @@ func NextD(M *WavefrontComponent, D *WavefrontComponent, score int, k int, penal
a_ok, a, _ := M.GetVal(score-o-e, k+1) a_ok, a, _ := M.GetVal(score-o-e, k+1)
b_ok, b, _ := D.GetVal(score-e, k+1) b_ok, b, _ := D.GetVal(score-e, k+1)
ok, nextDTraceback := SafeArgMax([]bool{a_ok, b_ok}, []uint32{a, b}) ok, nextDTraceback := SafeArgMax([]bool{a_ok, b_ok}, []uint64{a, b})
nextDVal := SafeMax([]uint32{a, b}, nextDTraceback) nextDVal := SafeMax([]uint64{a, b}, nextDTraceback)
if ok { if ok {
D.SetVal(score, k, nextDVal, []Traceback{OpenDel, ExtdDel}[nextDTraceback]) D.SetVal(score, k, nextDVal, []Traceback{OpenDel, ExtdDel}[nextDTraceback])
} }
} }
// set the traceback and diag value for the next M wavefront
func NextM(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponent, score int, k int, penalties Penalty) { func NextM(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponent, score int, k int, penalties Penalty) {
x := penalties.X x := penalties.X
@@ -161,8 +161,8 @@ func NextM(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponent,
b_ok, b, _ := I.GetVal(score, k) b_ok, b, _ := I.GetVal(score, k)
c_ok, c, _ := D.GetVal(score, k) c_ok, c, _ := D.GetVal(score, k)
ok, nextMTraceback := SafeArgMax([]bool{a_ok, b_ok, c_ok}, []uint32{a, b, c}) ok, nextMTraceback := SafeArgMax([]bool{a_ok, b_ok, c_ok}, []uint64{a, b, c})
nextMVal := SafeMax([]uint32{a, b, c}, nextMTraceback) nextMVal := SafeMax([]uint64{a, b, c}, nextMTraceback)
if ok { if ok {
M.SetVal(score, k, nextMVal, []Traceback{Sub, Ins, Del}[nextMTraceback]) M.SetVal(score, k, nextMVal, []Traceback{Sub, Ins, Del}[nextMTraceback])
} }
+21 -23
View File
@@ -1,14 +1,11 @@
package wfa package wfa
import ( // WFAlign takes strings s1, s2, penalties, and returns the score and CIGAR if doCIGAR is true
"strings"
)
func WFAlign(s1 string, s2 string, penalties Penalty, doCIGAR bool) Result { func WFAlign(s1 string, s2 string, penalties Penalty, doCIGAR bool) Result {
n := len(s1) n := len(s1)
m := len(s2) m := len(s2)
A_k := m - n A_k := m - n // diagonal where both sequences end
A_offset := uint32(m) A_offset := uint64(m) // offset along a_k diagonal corresponding to end
score := 0 score := 0
M := NewWavefrontComponent() M := NewWavefrontComponent()
M.SetLoHi(0, 0, 0) M.SetLoHi(0, 0, 0)
@@ -19,7 +16,7 @@ func WFAlign(s1 string, s2 string, penalties Penalty, doCIGAR bool) Result {
for { for {
WFExtend(M, s1, n, s2, m, score) WFExtend(M, s1, n, s2, m, score)
ok, val, _ := M.GetVal(score, A_k) ok, val, _ := M.GetVal(score, A_k)
if ok && val >= A_offset { if ok && val >= A_offset { // exit when M_(s,a_k) >= A_offset, ie the wavefront has reached the end
break break
} }
score = score + 1 score = score + 1
@@ -27,7 +24,7 @@ func WFAlign(s1 string, s2 string, penalties Penalty, doCIGAR bool) Result {
} }
CIGAR := "" CIGAR := ""
if doCIGAR { if doCIGAR { // if doCIGAR, then perform backtrace, otherwise just return the score
CIGAR = WFBacktrace(M, I, D, score, penalties, A_k, A_offset, s1, s2) CIGAR = WFBacktrace(M, I, D, score, penalties, A_k, A_offset, s1, s2)
} }
@@ -39,23 +36,24 @@ func WFAlign(s1 string, s2 string, penalties Penalty, doCIGAR bool) Result {
func WFExtend(M *WavefrontComponent, s1 string, n int, s2 string, m int, score int) { func WFExtend(M *WavefrontComponent, s1 string, n int, s2 string, m int, score int) {
_, lo, hi := M.GetLoHi(score) _, lo, hi := M.GetLoHi(score)
for k := lo; k <= hi; k++ { for k := lo; k <= hi; k++ { // for each diagonal in current wavefront
// v = M[score][k] - k // v = M[score][k] - k
// h = M[score][k] // h = M[score][k]
ok, hu, _ := M.GetVal(score, k) ok, uh, tb := M.GetVal(score, k)
h := int(hu) // exit early if M_(s,l) is invalid
v := h - k
// exit early if v or h are invalid
if !ok { if !ok {
continue continue
} }
for v < n && h < m && s1[v] == s2[h] { h := int(uh)
_, val, tb := M.GetVal(score, k) v := h - k
M.SetVal(score, k, val+1, tb) // in the paper, we do v++, h++, M_(s,k)++
// however, note that h = M_(s,k) so instead we just do v++, h++ and set M_(s,k) at the end
// this saves a some memory reads and writes
for v < n && h < m && s1[v] == s2[h] { // extend diagonal for the next set of matches
v++ v++
h++ h++
} }
M.SetVal(score, k, uint64(h), tb)
} }
} }
@@ -63,14 +61,14 @@ func WFNext(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponent,
// get this score's lo, hi // get this score's lo, hi
lo, hi := NextLoHi(M, I, D, score, penalties) lo, hi := NextLoHi(M, I, D, score, penalties)
for k := lo; k <= hi; k++ { for k := lo; k <= hi; k++ { // for each diagonal, extend the matrices for the next wavefronts
NextI(M, I, score, k, penalties) NextI(M, I, score, k, penalties)
NextD(M, D, score, k, penalties) NextD(M, D, score, k, penalties)
NextM(M, I, D, score, k, penalties) NextM(M, I, D, score, k, penalties)
} }
} }
func WFBacktrace(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponent, score int, penalties Penalty, A_k int, A_offset uint32, s1 string, s2 string) string { func WFBacktrace(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontComponent, score int, penalties Penalty, A_k int, A_offset uint64, s1 string, s2 string) string {
x := penalties.X x := penalties.X
o := penalties.O o := penalties.O
e := penalties.E e := penalties.E
@@ -187,11 +185,11 @@ func WFBacktrace(M *WavefrontComponent, I *WavefrontComponent, D *WavefrontCompo
} }
} }
CIGAR := strings.Builder{} CIGAR := ""
for i := len(Ops) - 1; i > 0; i-- { for i := len(Ops) - 1; i > 0; i-- {
CIGAR.WriteString(UIntToString(Counts[i])) CIGAR += UIntToString(Counts[i])
CIGAR.WriteRune(Ops[i]) CIGAR += string(Ops[i])
} }
return CIGAR.String() return CIGAR
} }
+2 -2
View File
@@ -36,8 +36,8 @@ func randRange[T constraints.Integer](min, max int) T {
func TestWavefrontPacking(t *testing.T) { func TestWavefrontPacking(t *testing.T) {
for range 1000 { for range 1000 {
val := randRange[uint32](0, 1000) val := randRange[uint64](0, 1000)
tb := wfa.Traceback(randRange[uint32](0, 7)) tb := wfa.Traceback(randRange[uint64](0, 7))
v := wfa.PackWavefrontValue(val, tb) v := wfa.PackWavefrontValue(val, tb)
valid, gotVal, gotTB := wfa.UnpackWavefrontValue(v) valid, gotVal, gotTB := wfa.UnpackWavefrontValue(v)
+63 -44
View File
@@ -1,5 +1,3 @@
// wasm_exec.js from tinygo
// Copyright 2018 The Go Authors. All rights reserved. // Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -134,6 +132,7 @@
const decoder = new TextDecoder("utf-8"); const decoder = new TextDecoder("utf-8");
let reinterpretBuf = new DataView(new ArrayBuffer(8)); let reinterpretBuf = new DataView(new ArrayBuffer(8));
var logLine = []; var logLine = [];
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
global.Go = class { global.Go = class {
constructor() { constructor() {
@@ -272,14 +271,11 @@
fd_close: () => 0, // dummy fd_close: () => 0, // dummy
fd_fdstat_get: () => 0, // dummy fd_fdstat_get: () => 0, // dummy
fd_seek: () => 0, // dummy fd_seek: () => 0, // dummy
"proc_exit": (code) => { proc_exit: (code) => {
if (global.process) { this.exited = true;
// Node.js this.exitCode = code;
process.exit(code); this._resolveExitPromise();
} else { throw wasmExit;
// Can't exit in a browser.
throw 'trying to exit with code ' + code;
}
}, },
random_get: (bufPtr, bufLen) => { random_get: (bufPtr, bufLen) => {
crypto.getRandomValues(loadSlice(bufPtr, bufLen)); crypto.getRandomValues(loadSlice(bufPtr, bufLen));
@@ -287,31 +283,45 @@
}, },
}, },
gojs: { gojs: {
// func ticks() float64 // func ticks() int64
"runtime.ticks": () => { "runtime.ticks": () => {
return timeOrigin + performance.now(); return BigInt((timeOrigin + performance.now()) * 1e6);
}, },
// func sleepTicks(timeout float64) // func sleepTicks(timeout int64)
"runtime.sleepTicks": (timeout) => { "runtime.sleepTicks": (timeout) => {
// Do not sleep, only reactivate scheduler after the given timeout. // Do not sleep, only reactivate scheduler after the given timeout.
setTimeout(this._inst.exports.go_scheduler, timeout); setTimeout(() => {
if (this.exited) return;
try {
this._inst.exports.go_scheduler();
} catch (e) {
if (e !== wasmExit) throw e;
}
}, Number(timeout)/1e6);
}, },
// func finalizeRef(v ref) // func finalizeRef(v ref)
"syscall/js.finalizeRef": (v_ref) => { "syscall/js.finalizeRef": (v_ref) => {
const id = mem().getUint32(unboxValue(v_ref), true); // Note: TinyGo does not support finalizers so this is only called
this._goRefCounts[id]--; // for one specific case, by js.go:jsString. and can/might leak memory.
if (this._goRefCounts[id] === 0) { const id = v_ref & 0xffffffffn;
const v = this._values[id]; if (this._goRefCounts?.[id] !== undefined) {
this._values[id] = null; this._goRefCounts[id]--;
this._ids.delete(v); if (this._goRefCounts[id] === 0) {
this._idPool.push(id); const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
} else {
console.error("syscall/js.finalizeRef: unknown id", id);
} }
}, },
// func stringVal(value string) ref // func stringVal(value string) ref
"syscall/js.stringVal": (value_ptr, value_len) => { "syscall/js.stringVal": (value_ptr, value_len) => {
value_ptr >>>= 0;
const s = loadString(value_ptr, value_len); const s = loadString(value_ptr, value_len);
return boxValue(s); return boxValue(s);
}, },
@@ -472,21 +482,25 @@
this._ids = new Map(); // mapping from JS values to reference ids this._ids = new Map(); // mapping from JS values to reference ids
this._idPool = []; // unused ids that have been garbage collected this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited this.exited = false; // whether the Go program has exited
this.exitCode = 0;
while (true) { if (this._inst.exports._start) {
const callbackPromise = new Promise((resolve) => { let exitPromise = new Promise((resolve, reject) => {
this._resolveCallbackPromise = () => { this._resolveExitPromise = resolve;
if (this.exited) {
throw new Error("bad callback: Go program has already exited");
}
setTimeout(resolve, 0); // make sure it is asynchronous
};
}); });
this._inst.exports._start();
if (this.exited) { // Run program, but catch the wasmExit exception that's thrown
break; // to return back here.
try {
this._inst.exports._start();
} catch (e) {
if (e !== wasmExit) throw e;
} }
await callbackPromise;
await exitPromise;
return this.exitCode;
} else {
this._inst.exports._initialize();
} }
} }
@@ -494,7 +508,11 @@
if (this.exited) { if (this.exited) {
throw new Error("Go program has already exited"); throw new Error("Go program has already exited");
} }
this._inst.exports.resume(); try {
this._inst.exports.resume();
} catch (e) {
if (e !== wasmExit) throw e;
}
if (this.exited) { if (this.exited) {
this._resolveExitPromise(); this._resolveExitPromise();
} }
@@ -524,8 +542,9 @@
} }
const go = new Go(); const go = new Go();
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
return go.run(result.instance); let exitCode = await go.run(result.instance);
process.exit(exitCode);
}).catch((err) => { }).catch((err) => {
console.error(err); console.error(err);
process.exit(1); process.exit(1);
@@ -537,22 +556,22 @@
export default function init (path) { export default function init (path) {
return new Promise ((res) => { return new Promise ((res) => {
const go = new Go(); const go = new Go();
var wasm; const run = (obj) => {
const wasm = obj.instance;
global.wfa = wasm
go.run(wasm);
res()
}
if ('instantiateStreaming' in WebAssembly) { if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(path), go.importObject).then(function (obj) { WebAssembly.instantiateStreaming(fetch(path), go.importObject).then(function (obj) {
wasm = obj.instance; run(obj)
go.run(wasm);
res()
}) })
} else { } else {
fetch(path).then(resp => fetch(path).then(resp =>
resp.arrayBuffer() resp.arrayBuffer()
).then(bytes => ).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) { WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance; run(obj)
go.run(wasm);
res()
}) })
) )
} }