-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfunction_template.go
More file actions
316 lines (287 loc) · 9.65 KB
/
function_template.go
File metadata and controls
316 lines (287 loc) · 9.65 KB
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
package v8go
// #include <stdlib.h>
// #include "function_template.h"
// #include "fast_api.h"
import "C"
import (
"fmt"
"runtime"
"unsafe"
)
// CType represents V8 Fast API argument/return types.
type CType int
const (
CTypeVoid CType = C.kCTypeVoid
CTypeBool CType = C.kCTypeBool
CTypeUint8 CType = C.kCTypeUint8
CTypeInt32 CType = C.kCTypeInt32
CTypeUint32 CType = C.kCTypeUint32
CTypeInt64 CType = C.kCTypeInt64
CTypeUint64 CType = C.kCTypeUint64
CTypeFloat32 CType = C.kCTypeFloat32
CTypeFloat64 CType = C.kCTypeFloat64
CTypePointer CType = C.kCTypePointer
CTypeV8Value CType = C.kCTypeV8Value
CTypeOneByteString CType = C.kCTypeSeqOneByteString
)
// FastCallDescriptor describes the signature of a V8 Fast API callback.
// FastFn is a C-linkage function pointer (unsafe.Pointer to a C function).
// ReturnType describes the return type. ArgTypes describes the parameter
// types (the first entry must be CTypeV8Value for the receiver).
type FastCallDescriptor struct {
FastFn unsafe.Pointer
ReturnType CType
ArgTypes []CType
}
// FunctionCallback is a callback that is executed in Go when a function is executed in JS.
type FunctionCallback func(info *FunctionCallbackInfo) *Value
// FunctionCallbackWithError is a callback that is executed in Go when
// a function is executed in JS. If a ValueError is returned, its
// value will be thrown as an exception in V8, otherwise Error() is
// invoked, and the string is thrown.
type FunctionCallbackWithError func(info *FunctionCallbackInfo) (*Value, error)
// FunctionCallbackInfo is the argument that is passed to a FunctionCallback.
type FunctionCallbackInfo struct {
ctx *Context
args []*Value
this *Object
}
// A ValueError can be returned from a FunctionCallbackWithError, and
// its value will be thrown as an exception in V8.
type ValueError interface {
error
Valuer
}
// Context is the current context that the callback is being executed in.
func (i *FunctionCallbackInfo) Context() *Context {
return i.ctx
}
// This returns the receiver object "this".
func (i *FunctionCallbackInfo) This() *Object {
return i.this
}
// Args returns a slice of the value arguments that are passed to the JS function.
func (i *FunctionCallbackInfo) Args() []*Value {
return i.args
}
func (i *FunctionCallbackInfo) Release() {
for _, arg := range i.args {
arg.Release()
}
i.this.Release()
}
// FunctionTemplate is used to create functions at runtime.
// There can only be one function created from a FunctionTemplate in a context.
// The lifetime of the created function is equal to the lifetime of the context.
//
// A FunctionTemplate can be used to create "constructors", and add methods to
// the "class". [FunctionTemplate.PrototypeTemplate] can be used to add normal
// methods on the class, and [FunctionTemplate.InstanceTemplate] can be used to
// add fields automatically to new instances of a class.
//
// V8 API Docs: https://v8.github.io/api/head/classv8_1_1FunctionTemplate.html
type FunctionTemplate struct {
*template
}
// NewFunctionTemplate creates a FunctionTemplate for a given
// callback. Prefer using NewFunctionTemplateWithError.
func NewFunctionTemplate(iso *Isolate, callback FunctionCallback) *FunctionTemplate {
if callback == nil {
panic("nil FunctionCallback argument not supported")
}
return NewFunctionTemplateWithError(iso, func(info *FunctionCallbackInfo) (*Value, error) {
return callback(info), nil
})
}
// NewFunctionTemplateWithError creates a FunctionTemplate for a given
// callback. If the callback returns an error, it will be thrown as a
// JS error.
func NewFunctionTemplateWithError(
iso *Isolate,
callback FunctionCallbackWithError,
) *FunctionTemplate {
if iso == nil {
panic("nil Isolate argument not supported")
}
if callback == nil {
panic("nil FunctionCallback argument not supported")
}
cbref := iso.registerCallback(callback)
tmpl := &template{
ptr: C.NewFunctionTemplate(iso.ptr, C.int(cbref)),
iso: iso,
}
runtime.SetFinalizer(tmpl, (*template).finalizer)
return &FunctionTemplate{tmpl}
}
// NewFastFunctionTemplate creates a FunctionTemplate with a V8 Fast API path.
// When TurboFan can prove the argument types match the descriptor at compile
// time, it calls the C function directly — bypassing the generic slow callback,
// CGo overhead, and all argument marshaling. The slow callback is still used
// for unoptimized calls and when types don't match.
//
// The fast function must:
// - Be a C-linkage function (not a Go function)
// - Not allocate on the JS heap
// - Not trigger JS execution
// - Be registered in the external_references array for snapshot compatibility
//
// The first argument of the fast function is always the receiver
// (v8::Local<v8::Object>).
func NewFastFunctionTemplate(
iso *Isolate,
slowCallback FunctionCallbackWithError,
fast FastCallDescriptor,
) *FunctionTemplate {
if iso == nil {
panic("nil Isolate argument not supported")
}
if slowCallback == nil {
panic("nil FunctionCallback argument not supported")
}
if fast.FastFn == nil {
panic("nil FastFn in descriptor not supported")
}
cbref := iso.registerCallback(slowCallback)
argTypes := make([]C.CTypeInfoType, len(fast.ArgTypes))
for i, t := range fast.ArgTypes {
argTypes[i] = C.CTypeInfoType(t)
}
var argPtr *C.CTypeInfoType
if len(argTypes) > 0 {
argPtr = &argTypes[0]
}
fnInfo := C.BuildCFunctionInfo(
C.CTypeInfoType(fast.ReturnType),
argPtr,
C.int(len(argTypes)),
)
tmpl := &template{
ptr: C.NewFastFunctionTemplate(iso.ptr, C.int(cbref), fast.FastFn, fnInfo),
iso: iso,
}
runtime.SetFinalizer(tmpl, (*template).finalizer)
return &FunctionTemplate{tmpl}
}
// GetFunction returns an instance of this function template bound to the given context.
func (tmpl *FunctionTemplate) GetFunction(ctx *Context) *Function {
rtn := C.FunctionTemplateGetFunction(tmpl.ptr, ctx.ptr)
runtime.KeepAlive(tmpl)
val, err := valueResult(ctx, rtn)
if err != nil {
panic(err) // TODO: Consider returning the error
}
return &Function{val}
}
// InstanceTemplate gets the [ObjectTemplate] that is used for new object
// instances created when this function is used as a constructor.
//
// You can add functions and values to new instance using [ObjectTemplate.Set]
// and [ObjectTemplate.SetSymbol]. Those values will become [own properties] on
// the instance, not the prototype.
//
// Adding a function to an instance template corresponds to the following
// JavaScript:
//
// class Example() {
// constructor() {
// this.foo = function() { /* creates a function on the instance */ }
// }
// }
//
// [own properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
func (tmpl *FunctionTemplate) InstanceTemplate() *ObjectTemplate {
result := &template{
ptr: C.FunctionTemplateInstanceTemplate(tmpl.ptr),
iso: tmpl.iso,
}
runtime.SetFinalizer(result, (*template).finalizer)
return &ObjectTemplate{result}
}
// PrototypeTemplate gets the [ObjectTemplate] that is used to create the
// prototype object associated with the function.
//
// You can call [ObjectTemplate.Set] or [ObjectTemplate.SetSymbol], passing a
// [FunctionTemplate] to add a "method" to the class.
//
// Adding a function to a prototype template corresponds normal method on a
// JavaScript "class":
//
// class Example {
// foo() { /* this is a method on the prototype */ }
// }
//
// Or the old-school way
//
// function Example() {}
// Example.prototype.foo = function() { }
//
// The function becomes an [own property] on the prototype, not the instance.
//
// [own property]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
func (tmpl *FunctionTemplate) PrototypeTemplate() *ObjectTemplate {
result := &template{
ptr: C.FunctionTemplatePrototypeTemplate(tmpl.ptr),
iso: tmpl.iso,
}
runtime.SetFinalizer(result, (*template).finalizer)
return &ObjectTemplate{result}
}
func (tmpl *FunctionTemplate) Inherit(base *FunctionTemplate) {
C.FunctionTemplateInherit(tmpl.ptr, base.ptr)
}
// Note that ideally `thisAndArgs` would be split into two separate arguments, but they were combined
// to workaround an ERROR_COMMITMENT_LIMIT error on windows that was detected in CI.
//
//export goFunctionCallback
func goFunctionCallback(
ctxref int,
cbref int,
thisAndArgs *C.ValuePtr,
argsCount int,
) (rval C.ValuePtr, rerr C.ValuePtr) {
ctx := getContext(ctxref)
this := *thisAndArgs
info := &FunctionCallbackInfo{
ctx: ctx,
this: &Object{&Value{ptr: this, ctx: ctx}},
args: make([]*Value, argsCount),
}
argv := (*[1 << 30]C.ValuePtr)(unsafe.Pointer(thisAndArgs))[1 : argsCount+1 : argsCount+1]
for i, v := range argv {
val := &Value{ptr: v, ctx: ctx}
info.args[i] = val
}
callbackFunc := ctx.iso.getCallback(cbref)
if callbackFunc == nil {
// The isolate has no Go closure registered for this callback
// reference. This commonly happens after restoring a snapshot
// that was produced with FunctionTemplates: the snapshot
// preserves the trampoline pointer (via external_references)
// and the integer ref, but Go-side closures must be
// re-registered on the consumer side. Return a JS error
// instead of nil-derefing.
errv, err := NewValue(ctx.iso, fmt.Sprintf(
"v8go: callback %d is not registered on this isolate; "+
"re-export functions after restoring a snapshot", cbref))
if err != nil {
return nil, nil
}
return nil, errv.ptr
}
val, err := callbackFunc(info)
if err != nil {
if verr, ok := err.(ValueError); ok {
return nil, verr.value().ptr
}
errv, err := NewValue(ctx.iso, err.Error())
if err != nil {
panic(err)
}
return nil, errv.ptr
}
if val == nil {
return nil, nil
}
return val.ptr, nil
}