summaryrefslogtreecommitdiff
path: root/internal/media/ffmpeg/wasm.go
blob: 29e5473649dbc9a6e5cd8e5967a5969b0fcd5d40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

//go:build !nowasmffmpeg && !nowasm

package ffmpeg

import (
	"context"
	"os"
	"runtime"
	"sync/atomic"
	"unsafe"

	"codeberg.org/gruf/go-ffmpreg/embed"
	"codeberg.org/gruf/go-ffmpreg/wasm"
	"github.com/tetratelabs/wazero"
	"golang.org/x/sys/cpu"
)

// ffmpreg is a concurrency-safe pointer
// to our necessary WebAssembly runtime
// and compiled ffmpreg module instance.
var ffmpreg atomic.Pointer[struct {
	run wazero.Runtime
	mod wazero.CompiledModule
}]

// initWASM safely prepares new WebAssembly runtime
// and compiles ffmpreg module instance, if the global
// pointer has not been already. else, is a no-op.
func initWASM(ctx context.Context) error {
	if ffmpreg.Load() != nil {
		return nil
	}

	var cfg wazero.RuntimeConfig

	// Create new runtime config, taking bug into account:
	// taking https://github.com/tetratelabs/wazero/pull/2365
	//
	// Thanks @ncruces (of go-sqlite3) for the fix!
	if compilerSupported() {
		cfg = wazero.NewRuntimeConfigCompiler()
	} else {
		cfg = wazero.NewRuntimeConfigInterpreter()
	}

	if dir := os.Getenv("GTS_WAZERO_COMPILATION_CACHE"); dir != "" {
		// Use on-filesystem compilation cache given by env.
		cache, err := wazero.NewCompilationCacheWithDir(dir)
		if err != nil {
			return err
		}

		// Update runtime config with cache.
		cfg = cfg.WithCompilationCache(cache)
	}

	var (
		run wazero.Runtime
		mod wazero.CompiledModule
		err error
		set bool
	)

	defer func() {
		if err == nil && set {
			// Drop binary.
			embed.B = nil
			return
		}

		// Close module.
		if !isNil(mod) {
			mod.Close(ctx)
		}

		// Close runtime.
		if !isNil(run) {
			run.Close(ctx)
		}
	}()

	// Initialize new runtime from config.
	run, err = wasm.NewRuntime(ctx, cfg)
	if err != nil {
		return err
	}

	// Compile ffmpreg WebAssembly into memory.
	mod, err = run.CompileModule(ctx, embed.B)
	if err != nil {
		return err
	}

	// Try set global WASM runtime and module,
	// or if beaten to it defer will handle close.
	set = ffmpreg.CompareAndSwap(nil, &struct {
		run wazero.Runtime
		mod wazero.CompiledModule
	}{
		run: run,
		mod: mod,
	})

	return nil
}

func compilerSupported() bool {
	switch runtime.GOOS {
	case "linux", "android",
		"windows", "darwin",
		"freebsd", "netbsd", "dragonfly",
		"solaris", "illumos":
		break
	default:
		return false
	}
	switch runtime.GOARCH {
	case "amd64":
		return cpu.X86.HasSSE41
	case "arm64":
		return true
	default:
		return false
	}
}

// isNil will safely check if 'v' is nil without
// dealing with weird Go interface nil bullshit.
func isNil(i interface{}) bool {
	type eface struct{ Type, Data unsafe.Pointer }
	return (*eface)(unsafe.Pointer(&i)).Data == nil
}