GitRoot

craft your forge, build your project, grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package plugin
  6
  7import (
  8	"bytes"
  9	"context"
 10	"crypto/rand"
 11	"errors"
 12	"fmt"
 13	"os"
 14	"path/filepath"
 15	"unicode/utf16"
 16
 17	"github.com/go-git/go-git/v5/plumbing"
 18	"github.com/samber/oops"
 19	"github.com/tetratelabs/wazero"
 20	"github.com/tetratelabs/wazero/api"
 21	"github.com/tetratelabs/wazero/imports/assemblyscript"
 22	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
 23	"github.com/tetratelabs/wazero/sys"
 24	pluginLib "gitroot.dev/libs/golang/plugin"
 25	grfs "gitroot.dev/server/fs"
 26	"gitroot.dev/server/logger"
 27	"gitroot.dev/server/repository"
 28)
 29
 30type runtimeInputsKind int
 31
 32const (
 33	runtimeInputsKindDiff     runtimeInputsKind = iota
 34	runtimeInputsKindWorktree runtimeInputsKind = iota
 35)
 36
 37type runtimeInputs struct {
 38	ctx      context.Context
 39	repoName string
 40	kind     runtimeInputsKind
 41	plugins  []Plugin
 42	commands []CommandForDiff
 43}
 44
 45type runtime struct {
 46	ctx          context.Context
 47	manager      *Manager
 48	logger       *logger.Logger
 49	repo         *repository.GitRootRepository
 50	repoWriter   *repository.GitRootRepositoryWrite
 51	plugin       Plugin
 52	pluginRun    PluginRun
 53	command      *CommandForDiff
 54	wazRun       wazero.Runtime
 55	fsWorktree   grfs.UpdatableFs
 56	fsWebcontent grfs.UpdatableFs
 57	fsCache      grfs.UpdatableFs
 58	commitHook   func(h plumbing.Hash)
 59}
 60
 61func newRuntime(ctx context.Context, manager *Manager, logger *logger.Logger) (*runtime, error) {
 62	r := &runtime{
 63		ctx:          ctx,
 64		manager:      manager,
 65		logger:       logger,
 66		wazRun:       wazero.NewRuntime(ctx),
 67		fsWorktree:   grfs.NewUpdatableFs(ctx, nil),
 68		fsWebcontent: grfs.NewUpdatableFs(ctx, nil),
 69		fsCache:      grfs.NewUpdatableFs(ctx, nil),
 70		commitHook:   nil,
 71	}
 72
 73	_, err := assemblyscript.Instantiate(ctx, r.wazRun)
 74	if err != nil {
 75		return nil, err
 76	}
 77
 78	_, err = r.wazRun.
 79		NewHostModuleBuilder("gitroot").
 80		NewFunctionBuilder().WithFunc(r.ForgeConf).Export("forgeConf").
 81		NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(false)).Export("modifyContent").
 82		NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(true)).Export("modifyContentAS").
 83		NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(false)).Export("modifyWebContent").
 84		NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(true)).Export("modifyWebContentAS").
 85		NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(false)).Export("replaceWebContent").
 86		NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(true)).Export("replaceWebContentAS").
 87		NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(false)).Export("modifyCacheContent").
 88		NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(true)).Export("modifyCacheContentAS").
 89		NewFunctionBuilder().WithFunc(r.CommitAllBuilder(false)).Export("commitAll").
 90		NewFunctionBuilder().WithFunc(r.CommitAllBuilder(true)).Export("commitAllAS").
 91		NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(false)).Export("diffWithParent").
 92		NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(true)).Export("diffWithParentAS").
 93		NewFunctionBuilder().WithFunc(r.LogBuilder(false)).Export("log").
 94		NewFunctionBuilder().WithFunc(r.LogBuilder(true)).Export("logAS").
 95		NewFunctionBuilder().WithFunc(r.LogErrorBuilder(false)).Export("logError").
 96		NewFunctionBuilder().WithFunc(r.LogErrorBuilder(true)).Export("logErrorAS").
 97		NewFunctionBuilder().WithFunc(r.MergeBuilder(false)).Export("merge").
 98		NewFunctionBuilder().WithFunc(r.MergeBuilder(true)).Export("mergeAS").
 99		Instantiate(ctx)
100	if err != nil {
101		return nil, err
102	}
103	_, err = wasi_snapshot_preview1.Instantiate(ctx, r.wazRun)
104	if err != nil {
105		return nil, err
106	}
107	return r, nil
108}
109
110func (r *runtime) listen(c chan runtimeInputs) {
111	for i := range c {
112		r.logger.Debug("listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree))
113		repo, err := r.manager.repoManager.Open(logger.AddCaller(r.ctx, "runtime.listen"), i.repoName)
114		if err != nil {
115			r.logger.Error("open error in listen", err)
116			repo.Close()
117			continue
118		}
119		repoWriter, err := repo.WillWrite(plumbing.HEAD) //TODO should mount good branc directly inside i.commands
120		if err != nil {
121			r.logger.Error("will write error in listen", err, logger.NewLoggerPair("repo", repo.Name()))
122			repo.Close()
123			continue
124		}
125
126		if i.kind == runtimeInputsKindDiff {
127			timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins", repo.Name()))
128			if err := r.start(i.ctx, repo, repoWriter, i.plugins, i.commands); err != nil {
129				r.logger.Error("start error", err)
130			}
131			timerStop()
132		} else if i.kind == runtimeInputsKindWorktree {
133			r.logger.Info("start worktree", logger.NewLoggerPair("repo", repo.Name()))
134			timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins worktree", repo.Name()))
135			if err := r.worktree(i.ctx, repo, repoWriter, i.plugins, i.commands); err != nil {
136				r.logger.Error("start error", err)
137			}
138			timerStop()
139		}
140		repo.Close()
141	}
142}
143
144func (r *runtime) conf(ctx context.Context, plugin Plugin) ([]pluginLib.PluginRun, error) {
145	r.fsWorktree.Clear()
146	r.fsWebcontent.Clear()
147	r.fsCache.Clear()
148	r.repo = nil
149	r.plugin = plugin
150	r.command = nil
151	timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
152
153	r.logger.Debug("start plugin conf", logger.NewLoggerPair("name", plugin.Name))
154	m := r.wazRun.Module(plugin.Name)
155	if m == nil {
156		r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Name))
157		config := wazero.NewModuleConfig().
158			WithStdout(os.Stdout).WithStderr(os.Stderr).
159			WithSysWalltime().
160			WithSysNanotime().
161			WithRandSource(rand.Reader).
162			WithFSConfig(
163				wazero.NewFSConfig().
164					WithFSMount(r.fsWorktree, "worktree").
165					WithFSMount(r.fsWebcontent, "webcontent").
166					WithFSMount(r.fsCache, "cache"))
167		mod, err := r.wazRun.InstantiateWithConfig(ctx, plugin.content, config.WithName(plugin.Name).WithStartFunctions("_initialize", "install"))
168		if err != nil {
169			if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
170				fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
171				return nil, err
172			} else if !ok {
173				return nil, err
174			}
175		}
176		m = mod
177	} else {
178		r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Name))
179	}
180	conf, err := r.callPluginForConf(ctx, m, r.logger.NewSubLogger(plugin.Name))
181	if err != nil {
182		r.logger.Error("finish plugin conf with error", err, logger.NewLoggerPair("name", plugin.Name))
183	}
184
185	r.logger.Debug("finish plugin conf", logger.NewLoggerPair("name", plugin.Name))
186	timerStop()
187	return conf, err
188}
189
190func (r *runtime) start(ctx context.Context, repo *repository.GitRootRepository, repoWriter *repository.GitRootRepositoryWrite, plugins []Plugin, commands []CommandForDiff) error {
191	r.fsWorktree.Update(repoWriter.ToFs(ctx))
192	r.fsWebcontent.Update(r.manager.conf.DataWeb(repo.Name()))
193
194	r.repo = repo
195	r.repoWriter = repoWriter
196	r.command = nil
197
198	r.commitHook = func(hash plumbing.Hash) {
199		command, err := repoWriter.GetLastCommit(hash)
200		if err != nil {
201			r.logger.Error("can't GetLastCommit in start runtime", err)
202			return
203		}
204		newCmd, err := CommandForDiffFromCommitCmd(ctx, repo, r.plugin, command, *r.command)
205		if err != nil {
206			r.logger.Error("can't CommandForDiffFromCommitCmd in start runtime", err)
207			return
208		}
209		commands = append(commands, newCmd)
210	}
211	defer func() {
212		r.commitHook = nil
213	}()
214
215	for _, plugin := range plugins {
216		r.fsCache.Update(r.manager.conf.Cache(repo.Name(), plugin.Name))
217		r.plugin = plugin
218		timerStop := r.logger.Time(fmt.Sprintf("Timer %s", plugin.Name))
219		r.logger.Debug("start plugin", logger.NewLoggerPair("name", plugin.Name))
220		m := r.wazRun.Module(plugin.Name)
221		if m == nil {
222			r.logger.Debug("instantiate plugin", logger.NewLoggerPair("name", plugin.Name))
223			config := wazero.NewModuleConfig().
224				WithSysWalltime().
225				WithSysNanotime().
226				WithRandSource(rand.Reader).
227				WithStdout(os.Stdout).WithStderr(os.Stderr).
228				WithFSConfig(wazero.NewFSConfig().
229					WithFSMount(r.fsWorktree, "worktree").
230					WithFSMount(r.fsWebcontent, "webcontent").
231					WithFSMount(r.fsCache, "cache"))
232			mod, err := r.wazRun.InstantiateWithConfig(ctx, plugin.content, config.WithName(plugin.Name).WithStartFunctions("_initialize", "install"))
233			if err != nil {
234				if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
235					fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode())
236					return nil
237				} else if !ok {
238					return err
239				}
240			}
241			m = mod
242		} else {
243			r.logger.Debug("already exist", logger.NewLoggerPair("name", plugin.Name))
244		}
245		l := logger.NewLogger(logger.WASM)
246		l.Debug("memory before", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
247		cp := callPlugin{
248			manager:    r.manager,
249			plugin:     plugin,
250			repo:       repo,
251			repoWriter: repoWriter,
252			module:     m,
253			logger:     r.logger.NewSubLogger(plugin.Name),
254		}
255		if err := cp.callPluginForDiff(ctx, r, commands); err != nil {
256			r.logger.Error("finish plugin with error", err, logger.NewLoggerPair("name", plugin.Name))
257		}
258		l.Debug("memory after", logger.NewLoggerPair("size", m.Memory().Size()), logger.NewLoggerPair("plugin", plugin.Name))
259		r.logger.Debug("finish plugin", logger.NewLoggerPair("name", plugin.Name))
260		timerStop()
261	}
262	return nil
263}
264
265func (r *runtime) ForgeConf(_ context.Context, m api.Module) uint64 {
266	// TODO need issues/a716-conf-external-addr.md to work
267	_, ptrSize, err := r.sendData(m, "{\"domain\": \"\"}") //TODO free ptr (first arg)
268	if err != nil {
269		r.logger.Error("ForgeConf can't send data", err)
270		return 0
271	}
272	return ptrSize
273}
274
275func (r *runtime) ModifyContentBuilder(forAS bool) interface{} {
276	f := func(filename string, content string) {
277		if ok, err := checkWrite(r.pluginRun.write.git, r.fsWorktree, filename); !ok {
278			r.logger.Error("plugin can't write in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
279			return
280		}
281		r.logger.Debug("modifyContent", logger.NewLoggerPair("file", filename))
282		if err := r.repoWriter.Write(filename, []byte(content)); err != nil {
283			r.logger.Error("modifyContent can't open file", err, logger.NewLoggerPair("filepath", filename))
284			return
285		}
286	}
287
288	if !forAS {
289		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
290			filename, err := r.readString(m, "ModifyContent filename", filenamePtr, filenameSize)
291			if err != nil {
292				return
293			}
294			content, err := r.readString(m, "ModifyContent content", contentPtr, contentSize)
295			if err != nil {
296				return
297			}
298			f(filename, content)
299		}
300	} else {
301		return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
302			filename, err := r.readASString(m, "ModifyContent filename", filenamePtr)
303			if err != nil {
304				return
305			}
306			content, err := r.readASString(m, "ModifyContent content", contentPtr)
307			if err != nil {
308				return
309			}
310			f(filename, content)
311		}
312	}
313}
314
315func (r *runtime) ModifyWebContentBuilder(forAS bool) interface{} {
316	f := func(filename string, content string) {
317		if ok, err := checkWrite(r.pluginRun.write.web, r.fsWebcontent, filename); !ok {
318			r.logger.Error("plugin can't write in web", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
319			return
320		}
321		fullPath := r.repo.PathDataWeb(filename)
322		dir, _ := filepath.Split(fullPath)
323		if err := os.MkdirAll(dir, os.ModePerm); err != nil {
324			r.logger.Error("modifyWebContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
325			return
326		}
327		if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
328			r.logger.Error("modifyWebContent can't open file", err, logger.NewLoggerPair("filepath", filename))
329			return
330		}
331		r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
332	}
333
334	if !forAS {
335		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
336			filename, err := r.readString(m, "ModifyWebContent filename", filenamePtr, filenameSize)
337			if err != nil {
338				return
339			}
340			content, err := r.readString(m, "ModifyWebContent content", contentPtr, contentSize)
341			if err != nil {
342				return
343			}
344			f(filename, content)
345		}
346	} else {
347		return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
348			filename, err := r.readASString(m, "ModifyWebContent filename", filenamePtr)
349			if err != nil {
350				return
351			}
352			content, err := r.readASString(m, "ModifyWebContent content", contentPtr)
353			if err != nil {
354				return
355			}
356			f(filename, content)
357		}
358	}
359}
360
361func (r *runtime) ReplaceWebContentBuilder(forAS bool) interface{} {
362	f := func(filename string, oldContent string, content string) {
363		if ok, err := checkWrite(r.pluginRun.write.web, r.fsWebcontent, filename); !ok {
364			r.logger.Error("plugin can't write in web", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename))
365			return
366		}
367		fullPath := r.repo.PathDataWeb(filename)
368		dir, _ := filepath.Split(fullPath)
369		if err := os.MkdirAll(dir, os.ModePerm); err != nil {
370			r.logger.Error("ReplaceWebContentBuilder can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
371			return
372		}
373		fileContent, err := os.ReadFile(fullPath)
374		if err != nil {
375			r.logger.Error("ReplaceWebContentBuilder can't ReadFile", err, logger.NewLoggerPair("filepath", filename))
376			return
377		}
378		if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil {
379			r.logger.Error("ReplaceWebContentBuilder can't open file", err, logger.NewLoggerPair("filepath", filename))
380			return
381		}
382		r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath))
383	}
384
385	if !forAS {
386		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) {
387			filename, err := r.readString(m, "ReplaceWebContentBuilder filename", filenamePtr, filenameSize)
388			if err != nil {
389				return
390			}
391			oldContent, err := r.readString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr, oldContentSize)
392			if err != nil {
393				return
394			}
395			content, err := r.readString(m, "ReplaceWebContentBuilder content", contentPtr, contentSize)
396			if err != nil {
397				return
398			}
399			f(filename, oldContent, content)
400		}
401	} else {
402		return func(_ context.Context, m api.Module, filenamePtr, oldContentPtr, contentPtr uint32) {
403			filename, err := r.readASString(m, "ReplaceWebContentBuilder filename", filenamePtr)
404			if err != nil {
405				return
406			}
407			oldContent, err := r.readASString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr)
408			if err != nil {
409				return
410			}
411			content, err := r.readASString(m, "ReplaceWebContentBuilder content", contentPtr)
412			if err != nil {
413				return
414			}
415			f(filename, oldContent, content)
416		}
417	}
418}
419
420func (r *runtime) ModifyCacheContentBuilder(forAS bool) interface{} {
421	f := func(filename string, content string) {
422		fullPath := filepath.Join(r.manager.conf.PathCache(), r.repo.Name(), r.plugin.Name, filename)
423		dir, _ := filepath.Split(fullPath)
424		if err := os.MkdirAll(dir, os.ModePerm); err != nil {
425			r.logger.Error("ModifyCacheContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename))
426			return
427		}
428		if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil {
429			r.logger.Error("ModifyCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename))
430			return
431		}
432		r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath))
433	}
434	if !forAS {
435		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) {
436			filename, err := r.readString(m, "ModifyCacheContent filename", filenamePtr, filenameSize)
437			if err != nil {
438				return
439			}
440			content, err := r.readString(m, "ModifyCacheContent content", contentPtr, contentSize)
441			if err != nil {
442				return
443			}
444			f(filename, content)
445		}
446	} else {
447		return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) {
448			filename, err := r.readASString(m, "ModifyCacheContent filename", filenamePtr)
449			if err != nil {
450				return
451			}
452			content, err := r.readASString(m, "ModifyCacheContent content", contentPtr)
453			if err != nil {
454				return
455			}
456			f(filename, content)
457		}
458	}
459}
460
461func (r *runtime) CommitAllBuilder(forAS bool) interface{} {
462	f := func(msg string, err error) {
463		if err != nil {
464			return
465		}
466		if h, err := r.repoWriter.CommitAll(msg, r.plugin.commiter); err != nil {
467			r.logger.Error("commitAll can't commit", err)
468			return
469		} else {
470			r.logger.Debug("Commit", logger.NewLoggerPair("message", msg), logger.NewLoggerPair("hash", h.String()))
471			if r.commitHook != nil && !h.IsZero() {
472				r.commitHook(h)
473			}
474		}
475	}
476	if !forAS {
477		return func(_ context.Context, m api.Module, offset, byteCount uint32) {
478			f(r.readString(m, "commitAll", offset, byteCount))
479		}
480	} else {
481		return func(_ context.Context, m api.Module, offset uint32) {
482			f(r.readASString(m, "commitAll", offset))
483		}
484	}
485}
486
487func (r *runtime) DiffWithParentBuilder(forAS bool) interface{} {
488	f := func(m api.Module, filepath string, hash string) uint64 {
489		r.logger.Info("In diffWithParent before found ancestor")
490		diffStr, err := r.repoWriter.GetDiff(plumbing.NewHash(hash), filepath)
491		if err != nil {
492			r.logger.Error("GetDiff", err)
493			return 0
494		}
495		r.logger.Debug("In diffWithParent send diff", logger.NewLoggerPair("diff", diffStr))
496		_, ptrSize, err := r.sendData(m, diffStr) //TODO free ptr (first arg)
497		if err != nil {
498			r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", diffStr))
499			return 0
500		}
501		return ptrSize
502	}
503	if !forAS {
504		return func(_ context.Context, m api.Module, filenamePtr, filenameSize, hashPtr, hashSize uint32) uint64 {
505			filename, err := r.readString(m, "DiffWithParent filename", filenamePtr, filenameSize)
506			if err != nil {
507				return 0
508			}
509			hash, err := r.readString(m, "DiffWithParent hash", hashPtr, hashSize)
510			if err != nil {
511				return 0
512			}
513			return f(m, filename, hash)
514		}
515	} else {
516		return func(_ context.Context, m api.Module, filenamePtr, hashPtr uint32) uint64 {
517			filename, err := r.readASString(m, "DiffWithParent filename", filenamePtr)
518			if err != nil {
519				return 0
520			}
521			hash, err := r.readASString(m, "DiffWithParent hash", hashPtr)
522			if err != nil {
523				return 0
524			}
525			return f(m, filename, hash)
526		}
527	}
528}
529
530func (r *runtime) LogBuilder(forAS bool) interface{} {
531	f := func(msg string, err error) {
532		if err != nil {
533			return
534		}
535		r.logger.Debug(msg, logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Name))
536	}
537	if !forAS {
538		return func(_ context.Context, m api.Module, offset, byteCount uint32) {
539			f(r.readString(m, "log", offset, byteCount))
540		}
541	} else {
542		return func(_ context.Context, m api.Module, offset uint32) {
543			f(r.readASString(m, "log", offset))
544		}
545	}
546}
547
548func (r *runtime) LogErrorBuilder(forAS bool) interface{} {
549	f := func(msg string, err string) {
550		r.logger.Error(msg, errors.New(err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Name))
551	}
552	if !forAS {
553		return func(_ context.Context, m api.Module, offset, byteCount, errPtr, errSize uint32) {
554			msg, err := r.readString(m, "LogError msg", offset, byteCount)
555			if err != nil {
556				return
557			}
558			errMsg, err := r.readString(m, "LogError err", errPtr, errSize)
559			if err != nil {
560				return
561			}
562			f(msg, errMsg)
563		}
564	} else {
565		return func(_ context.Context, m api.Module, offset, errPtr uint32) {
566			msg, err := r.readASString(m, "LogError msg", offset)
567			if err != nil {
568				return
569			}
570			errMsg, err := r.readASString(m, "LogError err", errPtr)
571			if err != nil {
572				return
573			}
574			f(msg, errMsg)
575		}
576	}
577}
578
579func (r *runtime) MergeBuilder(forAS bool) interface{} {
580	f := func(from string, to string) {
581		r.logger.Debug("try to merge", logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
582		if err := r.repoWriter.Merge(from, to, r.plugin.commiter, r.command.pusher); err != nil {
583			r.logger.Error("can't Merge", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to))
584		}
585	}
586	if !forAS {
587		return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) {
588			from, err := r.readString(m, "Merge from", fromPtr, fromSize)
589			if err != nil {
590				return
591			}
592			to, err := r.readString(m, "Merge to", toPtr, toSize)
593			if err != nil {
594				return
595			}
596			f(from, to)
597		}
598	} else {
599		return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) {
600			from, err := r.readASString(m, "Merge from", fromPtr)
601			if err != nil {
602				return
603			}
604			to, err := r.readASString(m, "Merge to", toPtr)
605			if err != nil {
606				return
607			}
608			f(from, to)
609		}
610	}
611}
612
613func (r *runtime) Close() error {
614	return r.wazRun.Close(r.ctx)
615}
616
617func (r *runtime) sendData(module api.Module, message string) (ptr uint32, ptrSize uint64, err error) {
618	malloc := module.ExportedFunction("malloc")
619
620	s := uint64(len(message))
621
622	results, err := malloc.Call(r.ctx, s)
623	if err != nil {
624		return 0, 0, oops.Wrapf(err, "can't malloc memory")
625	}
626	ptrA := results[0]
627
628	// The pointer is a linear memory offset, which is where we write the name.
629	if !module.Memory().WriteString(uint32(ptrA), message) {
630		return 0, 0, oops.Errorf("can't write memory")
631	}
632
633	return uint32(ptrA), (ptrA << uint64(32)) | s, nil
634}
635
636func (r *runtime) readString(m api.Module, errorMsg string, ptr uint32, size uint32) (string, error) {
637	txt, ok := m.Memory().Read(ptr, size)
638	if !ok {
639		err := errors.New("Memory.Read fail")
640		r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size))
641		return "", err
642	}
643	return string(txt), nil
644}
645
646func (r *runtime) readASString(m api.Module, errorMsg string, offset uint32) (string, error) {
647	// Length is four bytes before pointer.
648	byteCount, ok := m.Memory().ReadUint32Le(offset - 4)
649	if !ok || byteCount%2 != 0 {
650		err := errors.New("Memory.ReadUint32Le fail")
651		r.logger.Error(errorMsg, err, logger.NewLoggerPair("byteCount", byteCount), logger.NewLoggerPair("offset", offset))
652		return "", err
653	}
654	buf, ok := m.Memory().Read(offset, byteCount)
655	if !ok {
656		err := errors.New("Memory.Read fail")
657		r.logger.Error(errorMsg, err, logger.NewLoggerPair("offset", offset), logger.NewLoggerPair("byteCount", byteCount))
658		return "", err
659	}
660	u16s := make([]uint16, len(buf)/2)
661
662	lb := len(buf)
663	for i := 0; i < lb; i += 2 {
664		u16s[i/2] = uint16(buf[i]) + (uint16(buf[i+1]) << 8)
665	}
666	return string(utf16.Decode(u16s)), nil
667}