mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 04:23:41 +00:00
wasm: add //go:wasmexport support (#4451)
This adds support for the `//go:wasmexport` pragma as proposed here: https://github.com/golang/go/issues/65199 It is currently implemented only for wasip1 and wasm-unknown, but it is certainly possible to extend it to other targets like GOOS=js and wasip2.
This commit is contained in:
+10
-2
@@ -197,6 +197,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
ABI: config.ABI(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
BuildMode: config.BuildMode(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
SizeLevel: sizeLevel,
|
||||
@@ -649,6 +650,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
result.Binary = result.Executable // final file
|
||||
ldflags := append(config.LDFlags(), "-o", result.Executable)
|
||||
|
||||
if config.Options.BuildMode == "c-shared" {
|
||||
if !strings.HasPrefix(config.Triple(), "wasm32-") {
|
||||
return result, fmt.Errorf("buildmode c-shared is only supported on wasm at the moment")
|
||||
}
|
||||
ldflags = append(ldflags, "--no-entry")
|
||||
}
|
||||
|
||||
// Add compiler-rt dependency if needed. Usually this is a simple load from
|
||||
// a cache.
|
||||
if config.Target.RTLib == "compiler-rt" {
|
||||
@@ -880,7 +888,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("wasm-tools failed: %w", err)
|
||||
return fmt.Errorf("`wasm-tools component embed` failed: %w", err)
|
||||
}
|
||||
|
||||
// wasm-tools component new embedded.wasm -o component.wasm
|
||||
@@ -902,7 +910,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("wasm-tools failed: %w", err)
|
||||
return fmt.Errorf("`wasm-tools component new` failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,17 @@ func (c *Config) CPU() string {
|
||||
return c.Target.CPU
|
||||
}
|
||||
|
||||
// The current build mode (like the `-buildmode` command line flag).
|
||||
func (c *Config) BuildMode() string {
|
||||
if c.Options.BuildMode != "" {
|
||||
return c.Options.BuildMode
|
||||
}
|
||||
if c.Target.BuildMode != "" {
|
||||
return c.Target.BuildMode
|
||||
}
|
||||
return "default"
|
||||
}
|
||||
|
||||
// Features returns a list of features this CPU supports. For example, for a
|
||||
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
|
||||
// will be returned.
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
validBuildModeOptions = []string{"default", "c-shared"}
|
||||
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
|
||||
validSchedulerOptions = []string{"none", "tasks", "asyncify"}
|
||||
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
|
||||
@@ -26,6 +27,7 @@ type Options struct {
|
||||
GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle)
|
||||
Directory string // working dir, leave it unset to use the current working dir
|
||||
Target string
|
||||
BuildMode string // -buildmode flag
|
||||
Opt string
|
||||
GC string
|
||||
PanicStrategy string
|
||||
@@ -61,6 +63,14 @@ type Options struct {
|
||||
|
||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
||||
func (o *Options) Verify() error {
|
||||
if o.BuildMode != "" {
|
||||
valid := isInArray(validBuildModeOptions, o.BuildMode)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
|
||||
o.BuildMode,
|
||||
strings.Join(validBuildModeOptions, ", "))
|
||||
}
|
||||
}
|
||||
if o.GC != "" {
|
||||
valid := isInArray(validGCOptions, o.GC)
|
||||
if !valid {
|
||||
|
||||
@@ -32,6 +32,7 @@ type TargetSpec struct {
|
||||
GOARCH string `json:"goarch,omitempty"`
|
||||
SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc)
|
||||
BuildTags []string `json:"build-tags,omitempty"`
|
||||
BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified)
|
||||
GC string `json:"gc,omitempty"`
|
||||
Scheduler string `json:"scheduler,omitempty"`
|
||||
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
|
||||
|
||||
@@ -44,6 +44,7 @@ type Config struct {
|
||||
ABI string
|
||||
GOOS string
|
||||
GOARCH string
|
||||
BuildMode string
|
||||
CodeModel string
|
||||
RelocationModel string
|
||||
SizeLevel int
|
||||
@@ -1384,6 +1385,11 @@ func (b *builder) createFunction() {
|
||||
b.llvmFn.SetLinkage(llvm.InternalLinkage)
|
||||
b.createFunction()
|
||||
}
|
||||
|
||||
// Create wrapper function that can be called externally.
|
||||
if b.info.wasmExport != "" {
|
||||
b.createWasmExport()
|
||||
}
|
||||
}
|
||||
|
||||
// posser is an interface that's implemented by both ssa.Value and
|
||||
|
||||
+253
-20
@@ -7,6 +7,7 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -101,7 +102,7 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
|
||||
paramBundle := b.emitPointerPack(params)
|
||||
var stackSize llvm.Value
|
||||
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, instr.Pos())
|
||||
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
|
||||
if b.AutomaticStackSize {
|
||||
// The stack size is not known until after linking. Call a dummy
|
||||
// function that will be replaced with a load from a special ELF
|
||||
@@ -121,6 +122,147 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
|
||||
}
|
||||
|
||||
// Create an exported wrapper function for functions with the //go:wasmexport
|
||||
// pragma. This wrapper function is quite complex when the scheduler is enabled:
|
||||
// it needs to start a new goroutine each time the exported function is called.
|
||||
func (b *builder) createWasmExport() {
|
||||
pos := b.info.wasmExportPos
|
||||
if b.info.exported {
|
||||
// //export really shouldn't be used anymore when //go:wasmexport is
|
||||
// available, because //go:wasmexport is much better defined.
|
||||
b.addError(pos, "cannot use //export and //go:wasmexport at the same time")
|
||||
return
|
||||
}
|
||||
|
||||
const suffix = "#wasmexport"
|
||||
|
||||
// Declare the exported function.
|
||||
paramTypes := b.llvmFnType.ParamTypes()
|
||||
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
|
||||
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
|
||||
b.addStandardAttributes(exportedFn)
|
||||
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
|
||||
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
|
||||
|
||||
// Create a builder for this wrapper function.
|
||||
builder := newBuilder(b.compilerContext, b.ctx.NewBuilder(), b.fn)
|
||||
defer builder.Dispose()
|
||||
|
||||
// Define this function as a separate function in DWARF
|
||||
if b.Debug {
|
||||
if b.fn.Syntax() != nil {
|
||||
// Create debug info file if needed.
|
||||
pos := b.program.Fset.Position(pos)
|
||||
builder.difunc = builder.attachDebugInfoRaw(b.fn, exportedFn, suffix, pos.Filename, pos.Line)
|
||||
}
|
||||
builder.setDebugLocation(pos)
|
||||
}
|
||||
|
||||
// Create a single basic block inside of it.
|
||||
bb := llvm.AddBasicBlock(exportedFn, "entry")
|
||||
builder.SetInsertPointAtEnd(bb)
|
||||
|
||||
// Insert an assertion to make sure this //go:wasmexport function is not
|
||||
// called at a time when it is not allowed (for example, before the runtime
|
||||
// is initialized).
|
||||
builder.createRuntimeCall("wasmExportCheckRun", nil, "")
|
||||
|
||||
if b.Scheduler == "none" {
|
||||
// When the scheduler has been disabled, this is really trivial: just
|
||||
// call the function.
|
||||
params := exportedFn.Params()
|
||||
params = append(params, llvm.ConstNull(b.dataPtrType)) // context parameter
|
||||
retval := builder.CreateCall(b.llvmFnType, b.llvmFn, params, "")
|
||||
if b.fn.Signature.Results() == nil {
|
||||
builder.CreateRetVoid()
|
||||
} else {
|
||||
builder.CreateRet(retval)
|
||||
}
|
||||
|
||||
} else {
|
||||
// The scheduler is enabled, so we need to start a new goroutine, wait
|
||||
// for it to complete, and read the result value.
|
||||
|
||||
// Build a function that looks like this:
|
||||
//
|
||||
// func foo#wasmexport(param0, param1, ..., paramN) {
|
||||
// var state *stateStruct
|
||||
//
|
||||
// // 'done' must be explicitly initialized ('state' is not zeroed)
|
||||
// state.done = false
|
||||
//
|
||||
// // store the parameters in the state object
|
||||
// state.param0 = param0
|
||||
// state.param1 = param1
|
||||
// ...
|
||||
// state.paramN = paramN
|
||||
//
|
||||
// // create a goroutine and push it to the runqueue
|
||||
// task.start(uintptr(gowrapper), &state)
|
||||
//
|
||||
// // run the scheduler
|
||||
// runtime.wasmExportRun(&state.done)
|
||||
//
|
||||
// // if there is a return value, load it and return
|
||||
// return state.result
|
||||
// }
|
||||
|
||||
hasReturn := b.fn.Signature.Results() != nil
|
||||
|
||||
// Build the state struct type.
|
||||
// It stores the function parameters, the 'done' flag, and reserves
|
||||
// space for a return value if needed.
|
||||
stateFields := exportedFnType.ParamTypes()
|
||||
numParams := len(stateFields)
|
||||
stateFields = append(stateFields, b.ctx.Int1Type()) // 'done' field
|
||||
if hasReturn {
|
||||
stateFields = append(stateFields, b.llvmFnType.ReturnType())
|
||||
}
|
||||
stateStruct := b.ctx.StructType(stateFields, false)
|
||||
|
||||
// Allocate the state struct on the stack.
|
||||
statePtr := builder.CreateAlloca(stateStruct, "status")
|
||||
|
||||
// Initialize the 'done' field.
|
||||
doneGEP := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams), false),
|
||||
}, "done.gep")
|
||||
builder.CreateStore(llvm.ConstNull(b.ctx.Int1Type()), doneGEP)
|
||||
|
||||
// Store all parameters in the state object.
|
||||
for i, param := range exportedFn.Params() {
|
||||
gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
||||
}, "")
|
||||
builder.CreateStore(param, gep)
|
||||
}
|
||||
|
||||
// Create a new goroutine and add it to the runqueue.
|
||||
wrapper := b.createGoroutineStartWrapper(b.llvmFnType, b.llvmFn, "", false, true, pos)
|
||||
stackSize := llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
||||
taskStartFnType, taskStartFn := builder.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
||||
builder.createCall(taskStartFnType, taskStartFn, []llvm.Value{wrapper, statePtr, stackSize, llvm.Undef(b.dataPtrType)}, "")
|
||||
|
||||
// Run the scheduler.
|
||||
builder.createRuntimeCall("wasmExportRun", []llvm.Value{doneGEP}, "")
|
||||
|
||||
// Read the return value (if any) and return to the caller of the
|
||||
// //go:wasmexport function.
|
||||
if hasReturn {
|
||||
gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams)+1, false),
|
||||
}, "")
|
||||
retval := builder.CreateLoad(b.llvmFnType.ReturnType(), gep, "retval")
|
||||
builder.CreateRet(retval)
|
||||
} else {
|
||||
builder.CreateRetVoid()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// createGoroutineStartWrapper creates a wrapper for the task-based
|
||||
// implementation of goroutines. For example, to call a function like this:
|
||||
//
|
||||
@@ -144,7 +286,7 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
// to last parameter of the function) is used for this wrapper. If hasContext is
|
||||
// false, the parameter bundle is assumed to have no context parameter and undef
|
||||
// is passed instead.
|
||||
func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
|
||||
func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext, isWasmExport bool, pos token.Pos) llvm.Value {
|
||||
var wrapper llvm.Value
|
||||
|
||||
b := &builder{
|
||||
@@ -162,14 +304,18 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
if !fn.IsAFunction().IsNil() {
|
||||
// See whether this wrapper has already been created. If so, return it.
|
||||
name := fn.Name()
|
||||
wrapper = c.mod.NamedFunction(name + "$gowrapper")
|
||||
wrapperName := name + "$gowrapper"
|
||||
if isWasmExport {
|
||||
wrapperName += "-wasmexport"
|
||||
}
|
||||
wrapper = c.mod.NamedFunction(wrapperName)
|
||||
if !wrapper.IsNil() {
|
||||
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
||||
}
|
||||
|
||||
// Create the wrapper.
|
||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
|
||||
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
|
||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType)
|
||||
c.addStandardAttributes(wrapper)
|
||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
@@ -199,23 +345,110 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
// Create the list of params for the call.
|
||||
paramTypes := fnType.ParamTypes()
|
||||
if !hasContext {
|
||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
|
||||
}
|
||||
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
|
||||
if !hasContext {
|
||||
params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter
|
||||
}
|
||||
if !isWasmExport {
|
||||
// Regular 'go' instruction.
|
||||
|
||||
// Create the call.
|
||||
b.CreateCall(fnType, fn, params, "")
|
||||
// Create the list of params for the call.
|
||||
paramTypes := fnType.ParamTypes()
|
||||
if !hasContext {
|
||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
|
||||
}
|
||||
|
||||
if c.Scheduler == "asyncify" {
|
||||
b.CreateCall(deadlockType, deadlock, []llvm.Value{
|
||||
llvm.Undef(c.dataPtrType),
|
||||
}, "")
|
||||
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
|
||||
if !hasContext {
|
||||
params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter
|
||||
}
|
||||
|
||||
// Create the call.
|
||||
b.CreateCall(fnType, fn, params, "")
|
||||
|
||||
if c.Scheduler == "asyncify" {
|
||||
b.CreateCall(deadlockType, deadlock, []llvm.Value{
|
||||
llvm.Undef(c.dataPtrType),
|
||||
}, "")
|
||||
}
|
||||
} else {
|
||||
// Goroutine started from a //go:wasmexport pragma.
|
||||
// The function looks like this:
|
||||
//
|
||||
// func foo$gowrapper-wasmexport(state *stateStruct) {
|
||||
// // load values
|
||||
// param0 := state.params[0]
|
||||
// param1 := state.params[1]
|
||||
//
|
||||
// // call wrapped functions
|
||||
// result := foo(param0, param1, ...)
|
||||
//
|
||||
// // store result value (if there is any)
|
||||
// state.result = result
|
||||
//
|
||||
// // finish exported function
|
||||
// state.done = true
|
||||
// runtime.wasmExportExit()
|
||||
// }
|
||||
//
|
||||
// The state object here looks like:
|
||||
//
|
||||
// struct state {
|
||||
// param0
|
||||
// param1
|
||||
// param* // etc
|
||||
// done bool
|
||||
// result returnType
|
||||
// }
|
||||
|
||||
returnType := fnType.ReturnType()
|
||||
hasReturn := returnType != b.ctx.VoidType()
|
||||
statePtr := wrapper.Param(0)
|
||||
|
||||
// Create the state struct (it must match the type in createWasmExport).
|
||||
stateFields := fnType.ParamTypes()
|
||||
numParams := len(stateFields) - 1
|
||||
stateFields = stateFields[:numParams:numParams] // strip 'context' parameter
|
||||
stateFields = append(stateFields, c.ctx.Int1Type()) // 'done' bool
|
||||
if hasReturn {
|
||||
stateFields = append(stateFields, returnType)
|
||||
}
|
||||
stateStruct := b.ctx.StructType(stateFields, false)
|
||||
|
||||
// Extract parameters from the state object, and call the function
|
||||
// that's being wrapped.
|
||||
var callParams []llvm.Value
|
||||
for i := 0; i < numParams; i++ {
|
||||
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
||||
}, "")
|
||||
param := b.CreateLoad(stateFields[i], gep, "")
|
||||
callParams = append(callParams, param)
|
||||
}
|
||||
callParams = append(callParams, llvm.ConstNull(c.dataPtrType)) // add 'context' parameter
|
||||
result := b.CreateCall(fnType, fn, callParams, "")
|
||||
|
||||
// Store the return value back into the shared state.
|
||||
// Unlike regular goroutines, these special //go:wasmexport
|
||||
// goroutines can return a value.
|
||||
if hasReturn {
|
||||
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams)+1, false),
|
||||
}, "result.ptr")
|
||||
b.CreateStore(result, gep)
|
||||
}
|
||||
|
||||
// Mark this function as having finished executing.
|
||||
// This is important so the runtime knows the exported function
|
||||
// didn't block.
|
||||
doneGEP := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams), false),
|
||||
}, "done.gep")
|
||||
b.CreateStore(llvm.ConstInt(b.ctx.Int1Type(), 1, false), doneGEP)
|
||||
|
||||
// Call back into the runtime. This will exit the goroutine, switch
|
||||
// back to the scheduler, which will in turn return from the
|
||||
// //go:wasmexport function.
|
||||
b.createRuntimeCall("wasmExportExit", nil, "")
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -297,5 +530,5 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
}
|
||||
|
||||
// Return a ptrtoint of the wrapper, not the function itself.
|
||||
return b.CreatePtrToInt(wrapper, c.uintptrType, "")
|
||||
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
||||
}
|
||||
|
||||
+58
-18
@@ -23,15 +23,17 @@ import (
|
||||
// The linkName value contains a valid link name, even if //go:linkname is not
|
||||
// present.
|
||||
type functionInfo struct {
|
||||
wasmModule string // go:wasm-module
|
||||
wasmName string // wasm-export-name or wasm-import-name in the IR
|
||||
linkName string // go:linkname, go:export - the IR function name
|
||||
section string // go:section - object file section name
|
||||
exported bool // go:export, CGo
|
||||
interrupt bool // go:interrupt
|
||||
nobounds bool // go:nobounds
|
||||
variadic bool // go:variadic (CGo only)
|
||||
inline inlineType // go:inline
|
||||
wasmModule string // go:wasm-module
|
||||
wasmName string // wasm-export-name or wasm-import-name in the IR
|
||||
wasmExport string // go:wasmexport is defined (export is unset, this adds an exported wrapper)
|
||||
wasmExportPos token.Pos // position of //go:wasmexport comment
|
||||
linkName string // go:linkname, go:export - the IR function name
|
||||
section string // go:section - object file section name
|
||||
exported bool // go:export, CGo
|
||||
interrupt bool // go:interrupt
|
||||
nobounds bool // go:nobounds
|
||||
variadic bool // go:variadic (CGo only)
|
||||
inline inlineType // go:inline
|
||||
}
|
||||
|
||||
type inlineType int
|
||||
@@ -241,8 +243,22 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
// Pick the default linkName.
|
||||
linkName: f.RelString(nil),
|
||||
}
|
||||
|
||||
// Check for a few runtime functions that are treated specially.
|
||||
if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" {
|
||||
info.linkName = "_initialize"
|
||||
info.wasmName = "_initialize"
|
||||
info.exported = true
|
||||
}
|
||||
if info.linkName == "runtime.wasmEntryCommand" && c.BuildMode == "default" {
|
||||
info.linkName = "_start"
|
||||
info.wasmName = "_start"
|
||||
info.exported = true
|
||||
}
|
||||
|
||||
// Check for //go: pragmas, which may change the link name (among others).
|
||||
c.parsePragmas(&info, f)
|
||||
|
||||
c.functionInfos[f] = info
|
||||
return info
|
||||
}
|
||||
@@ -296,10 +312,39 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
c.checkWasmImport(f, comment.Text)
|
||||
if f.Blocks != nil {
|
||||
// Defined functions cannot be exported.
|
||||
c.addError(f.Pos(), "can only use //go:wasmimport on declarations")
|
||||
continue
|
||||
}
|
||||
c.checkWasmImportExport(f, comment.Text)
|
||||
info.exported = true
|
||||
info.wasmModule = parts[1]
|
||||
info.wasmName = parts[2]
|
||||
case "//go:wasmexport":
|
||||
if f.Blocks == nil {
|
||||
c.addError(f.Pos(), "can only use //go:wasmexport on definitions")
|
||||
continue
|
||||
}
|
||||
if len(parts) != 2 {
|
||||
c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmimport, not %d", len(parts)-1))
|
||||
continue
|
||||
}
|
||||
name := parts[1]
|
||||
if name == "_start" || name == "_initialize" {
|
||||
c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name))
|
||||
continue
|
||||
}
|
||||
if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" {
|
||||
c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode))
|
||||
continue
|
||||
}
|
||||
if c.archFamily() != "wasm32" {
|
||||
c.addError(f.Pos(), "//go:wasmexport is only supported on wasm")
|
||||
}
|
||||
c.checkWasmImportExport(f, comment.Text)
|
||||
info.wasmExport = name
|
||||
info.wasmExportPos = comment.Slash
|
||||
case "//go:inline":
|
||||
info.inline = inlineHint
|
||||
case "//go:noinline":
|
||||
@@ -346,22 +391,17 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether this function cannot be used in //go:wasmimport. It will add an
|
||||
// error if this is the case.
|
||||
// Check whether this function can be used in //go:wasmimport or
|
||||
// //go:wasmexport. It will add an error if this is not the case.
|
||||
//
|
||||
// The list of allowed types is based on this proposal:
|
||||
// https://github.com/golang/go/issues/59149
|
||||
func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) {
|
||||
func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) {
|
||||
if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" {
|
||||
// The runtime is a special case. Allow all kinds of parameters
|
||||
// (importantly, including pointers).
|
||||
return
|
||||
}
|
||||
if f.Blocks != nil {
|
||||
// Defined functions cannot be exported.
|
||||
c.addError(f.Pos(), "can only use //go:wasmimport on declarations")
|
||||
return
|
||||
}
|
||||
if f.Signature.Results().Len() > 1 {
|
||||
c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma))
|
||||
} else if f.Signature.Results().Len() == 1 {
|
||||
|
||||
@@ -14,6 +14,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-tty v0.0.4
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
||||
github.com/tetratelabs/wazero v1.6.0
|
||||
go.bug.st/serial v1.6.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sys v0.21.0
|
||||
|
||||
@@ -12,7 +12,6 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA
|
||||
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
@@ -45,19 +44,18 @@ github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3px
|
||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
|
||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g=
|
||||
github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A=
|
||||
go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -76,6 +74,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8 h1:bLsZXRUBavt++CJlMN7sppNziqu3LyamESLhFJcpqFQ=
|
||||
tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
|
||||
|
||||
@@ -1500,6 +1500,7 @@ func main() {
|
||||
var tags buildutil.TagsFlag
|
||||
flag.Var(&tags, "tags", "a space-separated list of extra build tags")
|
||||
target := flag.String("target", "", "chip/board name or JSON target specification file")
|
||||
buildMode := flag.String("buildmode", "", "build mode to use (default, c-shared)")
|
||||
var stackSize uint64
|
||||
flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error {
|
||||
size, err := bytesize.Parse(s)
|
||||
@@ -1608,6 +1609,7 @@ func main() {
|
||||
GOARM: goenv.Get("GOARM"),
|
||||
GOMIPS: goenv.Get("GOMIPS"),
|
||||
Target: *target,
|
||||
BuildMode: *buildMode,
|
||||
StackSize: stackSize,
|
||||
Opt: *opt,
|
||||
GC: *gc,
|
||||
|
||||
+187
@@ -6,6 +6,7 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"io"
|
||||
@@ -21,6 +22,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aykevl/go-wasm"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
|
||||
"github.com/tinygo-org/tinygo/builder"
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/diagnostics"
|
||||
@@ -524,6 +528,189 @@ func TestWebAssembly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWasmExport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
target string
|
||||
buildMode string
|
||||
scheduler string
|
||||
file string
|
||||
noOutput bool
|
||||
command bool // call _start (command mode) instead of _initialize
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
// "command mode" WASI
|
||||
{
|
||||
name: "WASIp1-command",
|
||||
target: "wasip1",
|
||||
command: true,
|
||||
},
|
||||
// "reactor mode" WASI (with -buildmode=c-shared)
|
||||
{
|
||||
name: "WASIp1-reactor",
|
||||
target: "wasip1",
|
||||
buildMode: "c-shared",
|
||||
},
|
||||
// Make sure reactor mode also works without a scheduler.
|
||||
{
|
||||
name: "WASIp1-reactor-noscheduler",
|
||||
target: "wasip1",
|
||||
buildMode: "c-shared",
|
||||
scheduler: "none",
|
||||
file: "wasmexport-noscheduler.go",
|
||||
},
|
||||
// Test -target=wasm-unknown with the default build mode (which is
|
||||
// c-shared).
|
||||
{
|
||||
name: "wasm-unknown-reactor",
|
||||
target: "wasm-unknown",
|
||||
file: "wasmexport-noscheduler.go",
|
||||
noOutput: true, // wasm-unknown cannot produce output
|
||||
},
|
||||
// Test -target=wasm-unknown with -buildmode=default, which makes it run
|
||||
// in command mode.
|
||||
{
|
||||
name: "wasm-unknown-command",
|
||||
target: "wasm-unknown",
|
||||
buildMode: "default",
|
||||
file: "wasmexport-noscheduler.go",
|
||||
noOutput: true, // wasm-unknown cannot produce output
|
||||
command: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Build the wasm binary.
|
||||
tmpdir := t.TempDir()
|
||||
options := optionsFromTarget(tc.target, sema)
|
||||
options.BuildMode = tc.buildMode
|
||||
options.Scheduler = tc.scheduler
|
||||
buildConfig, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
filename := "wasmexport.go"
|
||||
if tc.file != "" {
|
||||
filename = tc.file
|
||||
}
|
||||
result, err := builder.Build("testdata/"+filename, ".wasm", tmpdir, buildConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to build binary:", err)
|
||||
}
|
||||
|
||||
// Read the wasm binary back into memory.
|
||||
data, err := os.ReadFile(result.Binary)
|
||||
if err != nil {
|
||||
t.Fatal("could not read wasm binary: ", err)
|
||||
}
|
||||
|
||||
// Set up the wazero runtime.
|
||||
output := &bytes.Buffer{}
|
||||
ctx := context.Background()
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
defer r.Close(ctx)
|
||||
config := wazero.NewModuleConfig().
|
||||
WithStdout(output).WithStderr(output).
|
||||
WithStartFunctions()
|
||||
|
||||
// Prepare for testing.
|
||||
var mod api.Module
|
||||
mustCall := func(results []uint64, err error) []uint64 {
|
||||
if err != nil {
|
||||
t.Error("failed to run function:", err)
|
||||
}
|
||||
return results
|
||||
}
|
||||
checkResult := func(name string, results []uint64, expected []uint64) {
|
||||
if len(results) != len(expected) {
|
||||
t.Errorf("%s: expected %v but got %v", name, expected, results)
|
||||
}
|
||||
for i, result := range results {
|
||||
if result != expected[i] {
|
||||
t.Errorf("%s: expected %v but got %v", name, expected, results)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
runTests := func() {
|
||||
// Test an exported function without params or return value.
|
||||
checkResult("hello()", mustCall(mod.ExportedFunction("hello").Call(ctx)), nil)
|
||||
|
||||
// Test that we can call an exported function more than once.
|
||||
checkResult("add(3, 5)", mustCall(mod.ExportedFunction("add").Call(ctx, 3, 5)), []uint64{8})
|
||||
checkResult("add(7, 9)", mustCall(mod.ExportedFunction("add").Call(ctx, 7, 9)), []uint64{16})
|
||||
checkResult("add(6, 1)", mustCall(mod.ExportedFunction("add").Call(ctx, 6, 1)), []uint64{7})
|
||||
|
||||
// Test that imported functions can call exported functions
|
||||
// again.
|
||||
checkResult("reentrantCall(2, 3)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 2, 3)), []uint64{5})
|
||||
checkResult("reentrantCall(1, 8)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 1, 8)), []uint64{9})
|
||||
}
|
||||
|
||||
// Add wasip1 module.
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, r)
|
||||
|
||||
// Add custom "tester" module.
|
||||
callOutside := func(a, b int32) int32 {
|
||||
results, err := mod.ExportedFunction("add").Call(ctx, uint64(a), uint64(b))
|
||||
if err != nil {
|
||||
t.Error("could not call exported add function:", err)
|
||||
}
|
||||
return int32(results[0])
|
||||
}
|
||||
callTestMain := func() {
|
||||
runTests()
|
||||
}
|
||||
builder := r.NewHostModuleBuilder("tester")
|
||||
builder.NewFunctionBuilder().WithFunc(callOutside).Export("callOutside")
|
||||
builder.NewFunctionBuilder().WithFunc(callTestMain).Export("callTestMain")
|
||||
_, err = builder.Instantiate(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Parse and instantiate the wasm.
|
||||
mod, err = r.InstantiateWithConfig(ctx, data, config)
|
||||
if err != nil {
|
||||
t.Fatal("could not instantiate wasm module:", err)
|
||||
}
|
||||
|
||||
// Initialize the module and run the tests.
|
||||
if tc.command {
|
||||
// Call _start (the entry point), which calls
|
||||
// tester.callTestMain, which then runs all the tests.
|
||||
mustCall(mod.ExportedFunction("_start").Call(ctx))
|
||||
} else {
|
||||
// Run the _initialize call, because this is reactor mode wasm.
|
||||
mustCall(mod.ExportedFunction("_initialize").Call(ctx))
|
||||
runTests()
|
||||
}
|
||||
|
||||
// Check that the output matches the expected output.
|
||||
// (Skip this for wasm-unknown because it can't produce output).
|
||||
if !tc.noOutput {
|
||||
expectedOutput, err := os.ReadFile("testdata/wasmexport.txt")
|
||||
if err != nil {
|
||||
t.Fatal("could not read output file:", err)
|
||||
}
|
||||
actual := output.Bytes()
|
||||
expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n"))
|
||||
actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n"))
|
||||
if !bytes.Equal(actual, expectedOutput) {
|
||||
t.Error(string(Diff("expected", expectedOutput, "actual", actual)))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
//go:build tinygo.wasm && !wasm_unknown && !wasip2
|
||||
|
||||
// This file is for wasm/wasip1 and for wasm/js, which both use much of the
|
||||
// WASIp1 API.
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
|
||||
@@ -13,15 +13,6 @@ type timeUnit int64
|
||||
//export __wasm_call_ctors
|
||||
func __wasm_call_ctors()
|
||||
|
||||
//export _start
|
||||
func _start() {
|
||||
// These need to be initialized early so that the heap can be initialized.
|
||||
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
|
||||
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
|
||||
run()
|
||||
__stdio_exit()
|
||||
}
|
||||
|
||||
// Read the command line arguments from WASI.
|
||||
// For example, they can be passed to a program with wasmtime like this:
|
||||
//
|
||||
@@ -100,6 +91,10 @@ func ticks() timeUnit {
|
||||
return timeUnit(nano)
|
||||
}
|
||||
|
||||
func beforeExit() {
|
||||
__stdio_exit()
|
||||
}
|
||||
|
||||
// Implementations of WASI APIs
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 args_get
|
||||
|
||||
@@ -14,7 +14,7 @@ func resume() {
|
||||
}
|
||||
|
||||
wasmNested = true
|
||||
scheduler()
|
||||
scheduler(false)
|
||||
wasmNested = false
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ func go_scheduler() {
|
||||
}
|
||||
|
||||
wasmNested = true
|
||||
scheduler()
|
||||
scheduler(false)
|
||||
wasmNested = false
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
package runtime
|
||||
|
||||
import "unsafe"
|
||||
// TODO: this is essentially reactor mode wasm. So we might want to support
|
||||
// -buildmode=c-shared (and default to it).
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
@@ -11,14 +12,6 @@ type timeUnit int64
|
||||
//export __wasm_call_ctors
|
||||
func __wasm_call_ctors()
|
||||
|
||||
//export _initialize
|
||||
func _initialize() {
|
||||
// These need to be initialized early so that the heap can be initialized.
|
||||
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
|
||||
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
|
||||
initAll()
|
||||
}
|
||||
|
||||
func init() {
|
||||
__wasm_call_ctors()
|
||||
}
|
||||
@@ -40,3 +33,6 @@ func sleepTicks(d timeUnit) {
|
||||
func ticks() timeUnit {
|
||||
return timeUnit(0)
|
||||
}
|
||||
|
||||
func beforeExit() {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
//go:build tinygo.wasm && !wasip2 && !js
|
||||
|
||||
package runtime
|
||||
|
||||
// Entry points for WebAssembly modules, and runtime support for
|
||||
// //go:wasmexport: runtime.wasmExport* function calls are inserted by the
|
||||
// compiler for //go:wasmexport support.
|
||||
|
||||
import (
|
||||
"internal/task"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This is the _start entry point, when using -buildmode=default.
|
||||
func wasmEntryCommand() {
|
||||
// These need to be initialized early so that the heap can be initialized.
|
||||
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
|
||||
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
|
||||
wasmExportState = wasmExportStateInMain
|
||||
run()
|
||||
wasmExportState = wasmExportStateExited
|
||||
beforeExit()
|
||||
}
|
||||
|
||||
// This is the _initialize entry point, when using -buildmode=c-shared.
|
||||
func wasmEntryReactor() {
|
||||
// This function is called before any //go:wasmexport functions are called
|
||||
// to initialize everything. It must not block.
|
||||
|
||||
// Initialize the heap.
|
||||
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
|
||||
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
|
||||
initHeap()
|
||||
|
||||
if hasScheduler {
|
||||
// A package initializer might do funky stuff like start a goroutine and
|
||||
// wait until it completes, so we have to run package initializers in a
|
||||
// goroutine.
|
||||
go func() {
|
||||
initAll()
|
||||
wasmExportState = wasmExportStateReactor
|
||||
}()
|
||||
scheduler(true)
|
||||
if wasmExportState != wasmExportStateReactor {
|
||||
// Unlikely, but if package initializers do something blocking (like
|
||||
// time.Sleep()), that's a bug.
|
||||
runtimePanic("package initializer blocks")
|
||||
}
|
||||
} else {
|
||||
// There are no goroutines (except for the main one, if you can call it
|
||||
// that), so we can just run all the package initializers.
|
||||
initAll()
|
||||
wasmExportState = wasmExportStateReactor
|
||||
}
|
||||
}
|
||||
|
||||
// Track which state we're in: before (or during) init, running inside
|
||||
// main.main, after main.main returned, or reactor mode (after init).
|
||||
var wasmExportState uint8
|
||||
|
||||
const (
|
||||
wasmExportStateInit = iota
|
||||
wasmExportStateInMain
|
||||
wasmExportStateExited
|
||||
wasmExportStateReactor
|
||||
)
|
||||
|
||||
func wasmExportCheckRun() {
|
||||
switch wasmExportState {
|
||||
case wasmExportStateInit:
|
||||
runtimePanic("//go:wasmexport function called before runtime initialization")
|
||||
case wasmExportStateExited:
|
||||
runtimePanic("//go:wasmexport function called after main.main returned")
|
||||
}
|
||||
}
|
||||
|
||||
// Called from within a //go:wasmexport wrapper (the one that's exported from
|
||||
// the wasm module) after the goroutine has been queued. Just run the scheduler,
|
||||
// and check that the goroutine finished when the scheduler is idle (as required
|
||||
// by the //go:wasmexport proposal).
|
||||
//
|
||||
// This function is not called when the scheduler is disabled.
|
||||
func wasmExportRun(done *bool) {
|
||||
scheduler(true)
|
||||
if !*done {
|
||||
runtimePanic("//go:wasmexport function did not finish")
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the goroutine wrapper for the //go:wasmexport function. It just
|
||||
// signals to the runtime that the //go:wasmexport call has finished, and can
|
||||
// switch back to the wasmExportRun function.
|
||||
//
|
||||
// This function is not called when the scheduler is disabled.
|
||||
func wasmExportExit() {
|
||||
task.Pause()
|
||||
|
||||
// TODO: we could cache the allocated stack so we don't have to keep
|
||||
// allocating a new stack on every //go:wasmexport call.
|
||||
}
|
||||
@@ -157,7 +157,13 @@ func removeTimer(tim *timer) bool {
|
||||
}
|
||||
|
||||
// Run the scheduler until all tasks have finished.
|
||||
func scheduler() {
|
||||
// There are a few special cases:
|
||||
// - When returnAtDeadlock is true, it also returns when there are no more
|
||||
// runnable goroutines.
|
||||
// - When using the asyncify scheduler, it returns when it has to wait
|
||||
// (JavaScript uses setTimeout so the scheduler must return to the JS
|
||||
// environment).
|
||||
func scheduler(returnAtDeadlock bool) {
|
||||
// Main scheduler loop.
|
||||
var now timeUnit
|
||||
for !schedulerDone {
|
||||
@@ -193,6 +199,9 @@ func scheduler() {
|
||||
t := runqueue.Pop()
|
||||
if t == nil {
|
||||
if sleepQueue == nil && timerQueue == nil {
|
||||
if returnAtDeadlock {
|
||||
return
|
||||
}
|
||||
if asyncScheduler {
|
||||
// JavaScript is treated specially, see below.
|
||||
return
|
||||
|
||||
@@ -25,7 +25,7 @@ func run() {
|
||||
callMain()
|
||||
schedulerDone = true
|
||||
}()
|
||||
scheduler()
|
||||
scheduler(false)
|
||||
}
|
||||
|
||||
const hasScheduler = true
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"cpu": "generic",
|
||||
"features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,-bulk-memory",
|
||||
"build-tags": ["tinygo.wasm", "wasm_unknown"],
|
||||
"buildmode": "c-shared",
|
||||
"goos": "linux",
|
||||
"goarch": "arm",
|
||||
"linker": "wasm-ld",
|
||||
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
func init() {
|
||||
println("called init")
|
||||
}
|
||||
|
||||
//go:wasmimport tester callTestMain
|
||||
func callTestMain()
|
||||
|
||||
func main() {
|
||||
// main.main is not used when using -buildmode=c-shared.
|
||||
callTestMain()
|
||||
}
|
||||
|
||||
//go:wasmexport hello
|
||||
func hello() {
|
||||
println("hello!")
|
||||
}
|
||||
|
||||
//go:wasmexport add
|
||||
func add(a, b int) int {
|
||||
println("called add:", a, b)
|
||||
return a + b
|
||||
}
|
||||
|
||||
//go:wasmimport tester callOutside
|
||||
func callOutside(a, b int) int
|
||||
|
||||
//go:wasmexport reentrantCall
|
||||
func reentrantCall(a, b int) int {
|
||||
println("reentrantCall:", a, b)
|
||||
result := callOutside(a, b)
|
||||
println("reentrantCall result:", result)
|
||||
return result
|
||||
}
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
|
||||
func init() {
|
||||
println("called init")
|
||||
go adder()
|
||||
}
|
||||
|
||||
//go:wasmimport tester callTestMain
|
||||
func callTestMain()
|
||||
|
||||
func main() {
|
||||
// main.main is not used when using -buildmode=c-shared.
|
||||
callTestMain()
|
||||
}
|
||||
|
||||
//go:wasmexport hello
|
||||
func hello() {
|
||||
println("hello!")
|
||||
}
|
||||
|
||||
//go:wasmexport add
|
||||
func add(a, b int) int {
|
||||
println("called add:", a, b)
|
||||
addInputs <- a
|
||||
addInputs <- b
|
||||
return <-addOutput
|
||||
}
|
||||
|
||||
var addInputs = make(chan int)
|
||||
var addOutput = make(chan int)
|
||||
|
||||
func adder() {
|
||||
for {
|
||||
a := <-addInputs
|
||||
b := <-addInputs
|
||||
time.Sleep(time.Millisecond)
|
||||
addOutput <- a + b
|
||||
}
|
||||
}
|
||||
|
||||
//go:wasmimport tester callOutside
|
||||
func callOutside(a, b int) int
|
||||
|
||||
//go:wasmexport reentrantCall
|
||||
func reentrantCall(a, b int) int {
|
||||
println("reentrantCall:", a, b)
|
||||
result := callOutside(a, b)
|
||||
println("reentrantCall result:", result)
|
||||
return result
|
||||
}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
called init
|
||||
hello!
|
||||
called add: 3 5
|
||||
called add: 7 9
|
||||
called add: 6 1
|
||||
reentrantCall: 2 3
|
||||
called add: 2 3
|
||||
reentrantCall result: 5
|
||||
reentrantCall: 1 8
|
||||
called add: 1 8
|
||||
reentrantCall result: 9
|
||||
Reference in New Issue
Block a user