instructions.go raw
1 package jsbackend
2
3 import (
4 "fmt"
5 "go/constant"
6 "go/token"
7 "go/types"
8 "strings"
9
10 "golang.org/x/tools/go/ssa"
11 )
12
13 // FunctionCompiler handles the translation of a single SSA function to JS.
14 type FunctionCompiler struct {
15 pc *ProgramCompiler
16 fn *ssa.Function
17 e *Emitter
18 locals map[ssa.Value]string // SSA value -> JS variable name
19 varGen int // variable name counter
20 // Track which blocks need phi variable assignments.
21 phis []*ssa.Phi
22 }
23
24 // freshVar generates a unique local variable name.
25 func (fc *FunctionCompiler) freshVar(hint string) string {
26 fc.varGen++
27 if hint != "" {
28 return fmt.Sprintf("$%s_%d", JsIdentifier(hint), fc.varGen)
29 }
30 return fmt.Sprintf("$v_%d", fc.varGen)
31 }
32
33 // compile generates JS for the entire function.
34 func (fc *FunctionCompiler) compile() {
35 fn := fc.fn
36 e := fc.e
37
38 name := functionJsName(fn)
39 params := functionParams(fn)
40
41 // Determine if function needs to be async (contains channel ops, goroutine spawns, etc.).
42 async := fc.needsAsync()
43 hasDefer := fc.hasDefer()
44
45 prefix := "export "
46 if fn.Parent() != nil {
47 prefix = "" // nested/anonymous functions are not exported
48 }
49
50 asyncStr := ""
51 if async {
52 asyncStr = "async "
53 }
54
55 e.Block("%s%sfunction %s(%s)", prefix, asyncStr, name, params)
56
57 // Emit local variable declarations for all SSA values.
58 fc.emitLocals()
59
60 if hasDefer {
61 e.Line("const $defers = [];")
62 e.Line("let $panicValue = null;")
63 e.Block("try")
64 }
65
66 // Emit blocks.
67 // Use a block-label scheme: while(true) { switch($block) { case 0: ... } }
68 if len(fn.Blocks) == 1 {
69 // Simple function — no control flow needed.
70 fc.emitBlock(fn.Blocks[0], false)
71 } else if len(fn.Blocks) > 1 {
72 e.Line("let $block = 0;")
73 e.Block("while (true)")
74 e.Block("switch ($block)")
75
76 for _, block := range fn.Blocks {
77 // Skip the recover block — it's emitted after the try/catch/finally.
78 if hasDefer && fn.Recover != nil && block == fn.Recover {
79 continue
80 }
81 e.Line("case %d: {", block.Index)
82 e.Indent()
83 fc.emitBlock(block, true)
84 e.Line("break;")
85 e.Dedent()
86 e.Line("}")
87 }
88
89 e.EndBlock() // switch
90 e.EndBlock() // while
91 }
92
93 if hasDefer {
94 e.EndBlock() // try
95 e.Block("catch ($e)")
96 e.Line("$panicValue = $e;")
97 e.EndBlock() // catch
98 e.Block("finally")
99 e.Line("$rt.runtime.runDeferStack($defers, $panicValue);")
100 e.EndBlock() // finally
101
102 // If a panic was recovered, execution continues here.
103 // Emit the recover block which reads named return values.
104 if fn.Recover != nil {
105 e.Comment("Recover block — reached after successful recovery.")
106 fc.emitBlock(fn.Recover, false)
107 }
108 }
109
110 e.EndBlock() // function
111 e.Newline()
112
113 // Compile anonymous (closure) functions.
114 for _, anon := range fn.AnonFuncs {
115 fc.pc.compileFunction(e, anon)
116 }
117 }
118
119 // hasDefer checks if the function contains any defer statements.
120 func (fc *FunctionCompiler) hasDefer() bool {
121 for _, block := range fc.fn.Blocks {
122 for _, instr := range block.Instrs {
123 if _, ok := instr.(*ssa.Defer); ok {
124 return true
125 }
126 }
127 }
128 return false
129 }
130
131 // emitLocals declares JS variables for all SSA values used in the function.
132 func (fc *FunctionCompiler) emitLocals() {
133 // Parameters get their names from the SSA params.
134 for _, p := range fc.fn.Params {
135 fc.locals[p] = JsIdentifier(p.Name())
136 }
137
138 // Free variables (closures).
139 for _, fv := range fc.fn.FreeVars {
140 fc.locals[fv] = JsIdentifier(fv.Name())
141 }
142
143 // Scan all instructions for values that need variables.
144 var varDecls []string
145 for _, block := range fc.fn.Blocks {
146 for _, instr := range block.Instrs {
147 val, ok := instr.(ssa.Value)
148 if !ok {
149 continue
150 }
151 if _, exists := fc.locals[val]; exists {
152 continue
153 }
154 name := fc.freshVar(val.Name())
155 fc.locals[val] = name
156 varDecls = append(varDecls, name)
157 }
158 }
159 if len(varDecls) > 0 {
160 fc.e.Line("let %s;", strings.Join(varDecls, ", "))
161 }
162 }
163
164 // emitBlock emits JS for all instructions in a basic block.
165 func (fc *FunctionCompiler) emitBlock(block *ssa.BasicBlock, inSwitch bool) {
166 for _, instr := range block.Instrs {
167 fc.emitInstruction(instr, inSwitch)
168 }
169 }
170
171 // emitInstruction dispatches a single SSA instruction to JS code.
172 func (fc *FunctionCompiler) emitInstruction(instr ssa.Instruction, inSwitch bool) {
173 e := fc.e
174
175 switch instr := instr.(type) {
176 case *ssa.Alloc:
177 dst := fc.local(instr)
178 elemType := instr.Type().(*types.Pointer).Elem()
179 zero := fc.pc.typeMapper.ZeroExpr(elemType)
180 // Alloc creates a pointer. We model it as an accessor object.
181 if IsValueType(elemType) {
182 e.Line("%s = { $value: %s, $get() { return this.$value; }, $set(v) { this.$value = v; } };", dst, zero)
183 } else {
184 e.Line("%s = { $value: %s, $get() { return this.$value; }, $set(v) { this.$value = v; } };", dst, zero)
185 }
186
187 case *ssa.BinOp:
188 dst := fc.local(instr)
189 x := fc.value(instr.X)
190 y := fc.value(instr.Y)
191 op := fc.binOp(instr.Op, instr.X.Type())
192 e.Line("%s = %s;", dst, op(x, y))
193
194 case *ssa.Call:
195 dst := fc.local(instr)
196 callExpr := fc.emitCall(instr.Common())
197 if dst != "" && instr.Type() != nil {
198 e.Line("%s = %s;", dst, callExpr)
199 } else {
200 e.Line("%s;", callExpr)
201 }
202
203 case *ssa.ChangeInterface:
204 dst := fc.local(instr)
205 e.Line("%s = %s;", dst, fc.value(instr.X))
206
207 case *ssa.ChangeType:
208 dst := fc.local(instr)
209 e.Line("%s = %s;", dst, fc.value(instr.X))
210
211 case *ssa.Convert:
212 dst := fc.local(instr)
213 e.Line("%s = %s;", dst, fc.emitConvert(instr))
214
215 case *ssa.DebugRef:
216 // Skip.
217
218 case *ssa.Defer:
219 fc.emitDefer(instr)
220
221 case *ssa.Extract:
222 dst := fc.local(instr)
223 tuple := fc.value(instr.Tuple)
224 e.Line("%s = %s[%d];", dst, tuple, instr.Index)
225
226 case *ssa.Field:
227 dst := fc.local(instr)
228 x := fc.value(instr.X)
229 fieldName := fieldJsName(instr.X.Type(), instr.Field)
230 e.Line("%s = %s.%s;", dst, x, fieldName)
231
232 case *ssa.FieldAddr:
233 dst := fc.local(instr)
234 x := fc.value(instr.X)
235 fieldName := fieldJsName(instr.X.Type().(*types.Pointer).Elem(), instr.Field)
236 // Return an accessor that reads/writes the field.
237 e.Line("%s = { $get() { return %s.$get().%s; }, $set(v) { const obj = %s.$get(); obj.%s = v; %s.$set(obj); } };",
238 dst, x, fieldName, x, fieldName, x)
239
240 case *ssa.Go:
241 fc.emitGo(instr)
242
243 case *ssa.If:
244 cond := fc.value(instr.Cond)
245 block := instr.Block()
246 if inSwitch {
247 // Emit phi assignments before each branch target.
248 thenPhis := fc.phiAssignmentStmts(block, block.Succs[0])
249 elsePhis := fc.phiAssignmentStmts(block, block.Succs[1])
250 e.Block("if (%s)", cond)
251 for _, s := range thenPhis {
252 e.Line("%s", s)
253 }
254 e.Line("$block = %d; break;", block.Succs[0].Index)
255 e.EndBlock()
256 e.Block("else")
257 for _, s := range elsePhis {
258 e.Line("%s", s)
259 }
260 e.Line("$block = %d; break;", block.Succs[1].Index)
261 e.EndBlock()
262 } else {
263 e.Line("if (%s) { /* block %d */ } else { /* block %d */ }",
264 cond, block.Succs[0].Index, block.Succs[1].Index)
265 }
266
267 case *ssa.Index:
268 dst := fc.local(instr)
269 x := fc.value(instr.X)
270 idx := fc.value(instr.Index)
271 switch instr.X.Type().Underlying().(type) {
272 case *types.Basic: // string — use UTF-8 byte semantics
273 e.Line("$rt.runtime.boundsCheck(%s, $rt.builtin.byteLen(%s));", idx, x)
274 e.Line("%s = $rt.builtin.stringByteAt(%s, %s);", dst, x, idx)
275 default: // array/slice
276 e.Line("%s = %s.get(%s);", dst, x, idx)
277 }
278
279 case *ssa.IndexAddr:
280 dst := fc.local(instr)
281 x := fc.value(instr.X)
282 idx := fc.value(instr.Index)
283 // X is a pointer to an array or slice. Dereference through $get() for pointer types.
284 if _, ok := instr.X.Type().Underlying().(*types.Pointer); ok {
285 e.Line("%s = %s.$get().addr(%s);", dst, x, idx)
286 } else {
287 e.Line("%s = %s.addr(%s);", dst, x, idx)
288 }
289
290 case *ssa.Jump:
291 if inSwitch {
292 block := instr.Block()
293 // Emit phi assignments before jumping.
294 fc.emitPhiAssignments(block, block.Succs[0])
295 e.Line("$block = %d; break;", block.Succs[0].Index)
296 }
297
298 case *ssa.Lookup:
299 dst := fc.local(instr)
300 x := fc.value(instr.X)
301 idx := fc.value(instr.Index)
302
303 if _, ok := instr.X.Type().Underlying().(*types.Map); ok {
304 if instr.CommaOk {
305 e.Line("{ const $r = $rt.builtin.mapLookup(%s, %s); %s = [$r.value, $r.ok]; }", x, idx, dst)
306 } else {
307 e.Line("%s = $rt.builtin.mapLookup(%s, %s).value;", dst, x, idx)
308 }
309 } else {
310 // String index — UTF-8 byte semantics.
311 e.Line("%s = $rt.builtin.stringByteAt(%s, %s);", dst, x, idx)
312 }
313
314 case *ssa.MakeChan:
315 dst := fc.local(instr)
316 size := fc.value(instr.Size)
317 e.Line("%s = new $rt.channel.Channel(%s);", dst, size)
318
319 case *ssa.MakeClosure:
320 dst := fc.local(instr)
321 fnRef := fc.value(instr.Fn.(*ssa.Function))
322 var bindings []string
323 for _, b := range instr.Bindings {
324 bindings = append(bindings, fc.value(b))
325 }
326 e.Line("%s = %s.bind(null, %s);", dst, fnRef, strings.Join(bindings, ", "))
327
328 case *ssa.MakeInterface:
329 dst := fc.local(instr)
330 typeID := fc.pc.typeMapper.TypeID(instr.X.Type())
331 x := fc.value(instr.X)
332 e.Line("%s = $rt.types.makeInterface(%s, %s);", dst, JsString(typeID), x)
333
334 case *ssa.MakeMap:
335 dst := fc.local(instr)
336 mapType := instr.Type().Underlying().(*types.Map)
337 keyKind := fc.pc.typeMapper.KeyKind(mapType.Key())
338 e.Line("%s = $rt.builtin.makeMap(%s);", dst, JsString(keyKind))
339
340 case *ssa.MakeSlice:
341 dst := fc.local(instr)
342 length := fc.value(instr.Len)
343 capacity := fc.value(instr.Cap)
344 elemType := instr.Type().Underlying().(*types.Slice).Elem()
345 zero := fc.pc.typeMapper.ZeroExpr(elemType)
346 e.Line("%s = $rt.builtin.makeSlice(%s, %s, %s);", dst, length, capacity, zero)
347
348 case *ssa.MapUpdate:
349 m := fc.value(instr.Map)
350 k := fc.value(instr.Key)
351 v := fc.value(instr.Value)
352 e.Line("$rt.builtin.mapUpdate(%s, %s, %s);", m, k, v)
353
354 case *ssa.Next:
355 dst := fc.local(instr)
356 iter := fc.value(instr.Iter)
357 if instr.IsString {
358 e.Line("%s = %s.next();", dst, iter)
359 } else {
360 e.Line("%s = %s.next();", dst, iter)
361 }
362
363 case *ssa.Panic:
364 x := fc.value(instr.X)
365 e.Line("$rt.runtime.panic(%s);", x)
366
367 case *ssa.Phi:
368 // Phi nodes are handled via assignments at the end of predecessor blocks.
369 fc.phis = append(fc.phis, instr)
370
371 case *ssa.Range:
372 dst := fc.local(instr)
373 x := fc.value(instr.X)
374 switch instr.X.Type().Underlying().(type) {
375 case *types.Basic: // string range — use UTF-8 byte semantics
376 e.Line("%s = $rt.builtin.stringRange(%s);", dst, x)
377 case *types.Map:
378 e.Line("%s = { $entries: [...%s.entries()], $pos: 0, next() { if (this.$pos >= this.$entries.length) return [false, null, null]; const [k, v] = this.$entries[this.$pos++]; return [true, k, v]; } };", dst, x)
379 }
380
381 case *ssa.Return:
382 if len(instr.Results) == 0 {
383 e.Line("return;")
384 } else if len(instr.Results) == 1 {
385 e.Line("return %s;", fc.value(instr.Results[0]))
386 } else {
387 var vals []string
388 for _, r := range instr.Results {
389 vals = append(vals, fc.value(r))
390 }
391 e.Line("return [%s];", strings.Join(vals, ", "))
392 }
393
394 case *ssa.RunDefers:
395 e.Line("// RunDefers handled by try/finally")
396
397 case *ssa.Select:
398 fc.emitSelect(instr)
399
400 case *ssa.Send:
401 ch := fc.value(instr.Chan)
402 x := fc.value(instr.X)
403 e.Line("await %s.send(%s);", ch, x)
404
405 case *ssa.Slice:
406 dst := fc.local(instr)
407 x := fc.value(instr.X)
408 low := "undefined"
409 high := "undefined"
410 max := "undefined"
411 if instr.Low != nil {
412 low = fc.value(instr.Low)
413 }
414 if instr.High != nil {
415 high = fc.value(instr.High)
416 }
417 if instr.Max != nil {
418 max = fc.value(instr.Max)
419 }
420 xType := instr.X.Type().Underlying()
421 // Dereference pointer-to-array.
422 if ptr, ok := xType.(*types.Pointer); ok {
423 xType = ptr.Elem().Underlying()
424 x = x + ".$get()"
425 }
426 switch xType.(type) {
427 case *types.Basic: // string slice
428 e.Line("%s = $rt.builtin.stringSlice(%s, %s, %s);", dst, x, low, high)
429 default:
430 e.Line("%s = $rt.builtin.sliceSlice(%s, %s, %s, %s);", dst, x, low, high, max)
431 }
432
433 case *ssa.Store:
434 addr := fc.value(instr.Addr)
435 val := fc.value(instr.Val)
436 if needsClone(instr.Val.Type()) {
437 e.Line("%s.$set($rt.builtin.cloneValue(%s));", addr, val)
438 } else {
439 e.Line("%s.$set(%s);", addr, val)
440 }
441
442 case *ssa.TypeAssert:
443 dst := fc.local(instr)
444 x := fc.value(instr.X)
445 targetType := fc.pc.typeMapper.TypeID(instr.AssertedType)
446
447 if instr.CommaOk {
448 if types.IsInterface(instr.AssertedType) {
449 e.Line("{ try { %s = [$rt.types.interfaceAssert(%s, %s).$value, true]; } catch(e) { %s = [null, false]; } }",
450 dst, x, JsString(targetType), dst)
451 } else {
452 e.Line("%s = $rt.types.typeAssertOk(%s, %s);", dst, x, JsString(targetType))
453 }
454 } else {
455 if types.IsInterface(instr.AssertedType) {
456 e.Line("%s = $rt.types.interfaceAssert(%s, %s);", dst, x, JsString(targetType))
457 } else {
458 e.Line("%s = $rt.types.typeAssert(%s, %s);", dst, x, JsString(targetType))
459 }
460 }
461
462 case *ssa.UnOp:
463 fc.emitUnOp(instr)
464
465 default:
466 e.Comment("TODO: unhandled instruction: %T %s", instr, instr.String())
467 }
468 }
469
470 // phiAssignmentStmts returns JS assignment statements for phi nodes when jumping from src to dst.
471 // Uses temporaries to avoid sequential assignment corruption (all phis read old values simultaneously).
472 func (fc *FunctionCompiler) phiAssignmentStmts(src, dst *ssa.BasicBlock) []string {
473 type phiPair struct {
474 dst, val string
475 }
476 var pairs []phiPair
477 for _, instr := range dst.Instrs {
478 phi, ok := instr.(*ssa.Phi)
479 if !ok {
480 break
481 }
482 for i, pred := range dst.Preds {
483 if pred == src {
484 pairs = append(pairs, phiPair{fc.local(phi), fc.value(phi.Edges[i])})
485 break
486 }
487 }
488 }
489 if len(pairs) <= 1 {
490 // Single phi or none — no reordering issue.
491 var stmts []string
492 for _, p := range pairs {
493 stmts = append(stmts, fmt.Sprintf("%s = %s;", p.dst, p.val))
494 }
495 return stmts
496 }
497 // Check if any phi destination appears as a source in another phi.
498 dstSet := make(map[string]bool)
499 for _, p := range pairs {
500 dstSet[p.dst] = true
501 }
502 needsTemps := false
503 for _, p := range pairs {
504 if dstSet[p.val] {
505 needsTemps = true
506 break
507 }
508 }
509 if !needsTemps {
510 var stmts []string
511 for _, p := range pairs {
512 stmts = append(stmts, fmt.Sprintf("%s = %s;", p.dst, p.val))
513 }
514 return stmts
515 }
516 // Use temporaries: save all sources, then assign.
517 var stmts []string
518 for i, p := range pairs {
519 stmts = append(stmts, fmt.Sprintf("let $phi%d = %s;", i, p.val))
520 }
521 for i, p := range pairs {
522 stmts = append(stmts, fmt.Sprintf("%s = $phi%d;", p.dst, i))
523 }
524 return stmts
525 }
526
527 // emitPhiAssignments emits variable assignments for phi nodes when jumping from src to dst.
528 func (fc *FunctionCompiler) emitPhiAssignments(src, dst *ssa.BasicBlock) {
529 stmts := fc.phiAssignmentStmts(src, dst)
530 for _, s := range stmts {
531 fc.e.Line("%s", s)
532 }
533 }
534
535 // emitCall generates JS for a function call.
536 func (fc *FunctionCompiler) emitCall(call *ssa.CallCommon) string {
537 if call.IsInvoke() {
538 // Interface method call — await if caller is async (method may be async).
539 recv := fc.value(call.Value)
540 var args []string
541 for _, a := range call.Args {
542 args = append(args, fc.value(a))
543 }
544 awaitPrefix := ""
545 if fc.needsAsync() {
546 awaitPrefix = "await "
547 }
548 return fmt.Sprintf("%s$rt.types.methodCall(%s, %s, [%s])",
549 awaitPrefix, recv, JsString(call.Method.Name()), strings.Join(args, ", "))
550 }
551
552 callee := call.StaticCallee()
553 if callee != nil && len(callee.FreeVars) == 0 {
554 return fc.emitStaticCall(callee, call.Args)
555 }
556 // Closure call: callee has FreeVars that were pre-bound via MakeClosure.
557 // Fall through to the dynamic call path so we invoke the bound value
558 // instead of the bare function name (which would skip the bindings).
559
560 // Builtin call (println, len, cap, append, etc.).
561 if builtin, ok := call.Value.(*ssa.Builtin); ok {
562 return fc.emitBuiltinCall(builtin.Name(), call.Args)
563 }
564
565 // Dynamic call (function value) — await if caller is async.
566 fnVal := fc.value(call.Value)
567 var args []string
568 for _, a := range call.Args {
569 args = append(args, fc.value(a))
570 }
571 if fc.needsAsync() {
572 return fmt.Sprintf("await %s(%s)", fnVal, strings.Join(args, ", "))
573 }
574 return fmt.Sprintf("%s(%s)", fnVal, strings.Join(args, ", "))
575 }
576
577 // emitStaticCall generates JS for a static function call.
578 func (fc *FunctionCompiler) emitStaticCall(callee *ssa.Function, args []ssa.Value) string {
579 var jsArgs []string
580 for _, a := range args {
581 jsArgs = append(jsArgs, fc.value(a))
582 }
583 argStr := strings.Join(jsArgs, ", ")
584
585 fullName := callee.String()
586
587 // Map well-known functions to JS equivalents.
588 switch fullName {
589 case "println":
590 return fmt.Sprintf("$rt.runtime.println(%s)", argStr)
591 case "print":
592 return fmt.Sprintf("$rt.runtime.print(%s)", argStr)
593 case "len":
594 return fmt.Sprintf("$rt.builtin.len(%s)", argStr)
595 case "cap":
596 return fmt.Sprintf("$rt.builtin.cap(%s)", argStr)
597 case "append":
598 return fmt.Sprintf("$rt.builtin.append(%s)", argStr)
599 case "copy":
600 return fmt.Sprintf("$rt.builtin.copy(%s)", argStr)
601 case "delete":
602 return fmt.Sprintf("$rt.builtin.mapDelete(%s)", argStr)
603 case "close":
604 if len(jsArgs) > 0 {
605 return fmt.Sprintf("%s.close()", jsArgs[0])
606 }
607 case "panic":
608 return fmt.Sprintf("$rt.runtime.panic(%s)", argStr)
609 case "recover":
610 return "$rt.runtime.recover()"
611 case "spawn":
612 return fc.emitSpawn(callee, args)
613 }
614
615 // Package-qualified call.
616 fnName := functionJsName(callee)
617 pkg := callee.Package()
618
619 // Determine if the call needs to be awaited.
620 awaitPrefix := ""
621 if fc.needsAsync() && fc.pc.asyncFuncs[callee] {
622 awaitPrefix = "await "
623 }
624
625 if pkg != nil && pkg.Pkg.Path() != fc.fn.Package().Pkg.Path() {
626 // Cross-package call.
627 pkgAlias := JsIdentifier(pkg.Pkg.Path())
628 if shouldSkipPackage(pkg.Pkg.Path()) {
629 // Runtime function — map to JS runtime.
630 return fc.mapRuntimeCall(fullName, argStr)
631 }
632 return fmt.Sprintf("%s%s.%s(%s)", awaitPrefix, pkgAlias, fnName, argStr)
633 }
634
635 // Same-package call.
636 return fmt.Sprintf("%s%s(%s)", awaitPrefix, fnName, argStr)
637 }
638
639 // mapRuntimeCall maps Go runtime function calls to JS runtime equivalents.
640 func (fc *FunctionCompiler) mapRuntimeCall(fullName string, argStr string) string {
641 switch {
642 case strings.HasPrefix(fullName, "fmt.Println"), fullName == "fmt.Println":
643 return fmt.Sprintf("$rt.runtime.println(%s)", argStr)
644 case strings.HasPrefix(fullName, "fmt.Printf"), fullName == "fmt.Printf":
645 return fmt.Sprintf("$rt.runtime.print(%s)", argStr)
646 case strings.HasPrefix(fullName, "fmt.Sprintf"):
647 return fmt.Sprintf("String(%s)", argStr)
648 default:
649 return fmt.Sprintf("/* runtime: %s */ undefined", fullName)
650 }
651 }
652
653 // emitBuiltinCall handles calls to Go builtin functions.
654 func (fc *FunctionCompiler) emitBuiltinCall(name string, args []ssa.Value) string {
655 var jsArgs []string
656 for _, a := range args {
657 jsArgs = append(jsArgs, fc.value(a))
658 }
659 argStr := strings.Join(jsArgs, ", ")
660
661 switch name {
662 case "println":
663 return fmt.Sprintf("$rt.runtime.println(%s)", argStr)
664 case "print":
665 return fmt.Sprintf("$rt.runtime.print(%s)", argStr)
666 case "len":
667 return fmt.Sprintf("$rt.builtin.len(%s)", argStr)
668 case "cap":
669 return fmt.Sprintf("$rt.builtin.cap(%s)", argStr)
670 case "append":
671 // SSA append: first arg is the slice, second arg is also a slice (append(s1, s2...)).
672 if len(args) == 2 {
673 _, isSlice := args[1].Type().Underlying().(*types.Slice)
674 if isSlice {
675 return fmt.Sprintf("$rt.builtin.appendSlice(%s, %s)", jsArgs[0], jsArgs[1])
676 }
677 // append([]byte, string...) — spread string bytes.
678 if basic, ok := args[1].Type().Underlying().(*types.Basic); ok && basic.Info()&types.IsString != 0 {
679 return fmt.Sprintf("$rt.builtin.appendString(%s, %s)", jsArgs[0], jsArgs[1])
680 }
681 }
682 return fmt.Sprintf("$rt.builtin.append(%s)", argStr)
683 case "copy":
684 return fmt.Sprintf("$rt.builtin.copy(%s)", argStr)
685 case "delete":
686 return fmt.Sprintf("$rt.builtin.mapDelete(%s)", argStr)
687 case "close":
688 if len(jsArgs) > 0 {
689 return fmt.Sprintf("%s.close()", jsArgs[0])
690 }
691 return "undefined"
692 case "panic":
693 return fmt.Sprintf("$rt.runtime.panic(%s)", argStr)
694 case "recover":
695 return "$rt.runtime.recover()"
696 case "real":
697 if len(jsArgs) > 0 {
698 return fmt.Sprintf("(%s).re", jsArgs[0])
699 }
700 return "0"
701 case "imag":
702 if len(jsArgs) > 0 {
703 return fmt.Sprintf("(%s).im", jsArgs[0])
704 }
705 return "0"
706 case "complex":
707 if len(jsArgs) >= 2 {
708 return fmt.Sprintf("{ re: %s, im: %s }", jsArgs[0], jsArgs[1])
709 }
710 return "{ re: 0, im: 0 }"
711 case "min":
712 if len(jsArgs) == 2 {
713 return fmt.Sprintf("Math.min(%s, %s)", jsArgs[0], jsArgs[1])
714 }
715 return fmt.Sprintf("Math.min(%s)", argStr)
716 case "max":
717 if len(jsArgs) == 2 {
718 return fmt.Sprintf("Math.max(%s, %s)", jsArgs[0], jsArgs[1])
719 }
720 return fmt.Sprintf("Math.max(%s)", argStr)
721 case "make":
722 // Make is handled by MakeChan, MakeMap, MakeSlice SSA nodes, not as a call.
723 return fmt.Sprintf("/* make(%s) */", argStr)
724 case "new":
725 return fmt.Sprintf("/* new(%s) */", argStr)
726 default:
727 return fmt.Sprintf("/* builtin %s(%s) */", name, argStr)
728 }
729 }
730
731 // emitUnOp handles unary operations.
732 func (fc *FunctionCompiler) emitUnOp(instr *ssa.UnOp) {
733 dst := fc.local(instr)
734 x := fc.value(instr.X)
735
736 switch instr.Op {
737 case token.NOT:
738 fc.e.Line("%s = !%s;", dst, x)
739 case token.SUB:
740 fc.e.Line("%s = -%s;", dst, x)
741 case token.XOR:
742 fc.e.Line("%s = ~%s;", dst, x)
743 case token.MUL: // pointer dereference
744 fc.e.Line("%s = %s.$get();", dst, x)
745 case token.ARROW: // channel receive
746 if instr.CommaOk {
747 fc.e.Line("{ const $r = await %s.recv(); %s = [$r.value, $r.ok]; }", x, dst)
748 } else {
749 fc.e.Line("%s = (await %s.recv()).value;", dst, x)
750 }
751 default:
752 fc.e.Comment("TODO: unhandled UnOp: %s", instr.Op)
753 }
754 }
755
756 // emitDefer generates JS for a defer statement.
757 func (fc *FunctionCompiler) emitDefer(instr *ssa.Defer) {
758 call := &instr.Call
759 if call.IsInvoke() {
760 recv := fc.value(call.Value)
761 var args []string
762 for _, a := range call.Args {
763 args = append(args, fc.value(a))
764 }
765 fc.e.Line("$defers.push(() => $rt.types.methodCall(%s, %s, [%s]));",
766 recv, JsString(call.Method.Name()), strings.Join(args, ", "))
767 } else if builtin, ok := call.Value.(*ssa.Builtin); ok {
768 // Builtin call (println, etc.).
769 callExpr := fc.emitBuiltinCall(builtin.Name(), call.Args)
770 fc.e.Line("$defers.push(() => %s);", callExpr)
771 } else if _, ok := call.Value.(*ssa.MakeClosure); ok {
772 // Closure — use the bound closure variable.
773 fnVal := fc.value(call.Value)
774 var args []string
775 for _, a := range call.Args {
776 args = append(args, fc.value(a))
777 }
778 fc.e.Line("$defers.push(() => %s(%s));", fnVal, strings.Join(args, ", "))
779 } else if callee := call.StaticCallee(); callee != nil {
780 callExpr := fc.emitStaticCall(callee, call.Args)
781 fc.e.Line("$defers.push(() => %s);", callExpr)
782 } else {
783 fnVal := fc.value(call.Value)
784 var args []string
785 for _, a := range call.Args {
786 args = append(args, fc.value(a))
787 }
788 fc.e.Line("$defers.push(() => %s(%s));", fnVal, strings.Join(args, ", "))
789 }
790 }
791
792 // emitGo generates JS for a go statement (goroutine spawn).
793 func (fc *FunctionCompiler) emitGo(instr *ssa.Go) {
794 call := &instr.Call
795 if call.IsInvoke() {
796 recv := fc.value(call.Value)
797 var args []string
798 for _, a := range call.Args {
799 args = append(args, fc.value(a))
800 }
801 fc.e.Line("$rt.goroutine.spawn(async () => $rt.types.methodCall(%s, %s, [%s]));",
802 recv, JsString(call.Method.Name()), strings.Join(args, ", "))
803 } else if _, isClosure := call.Value.(*ssa.MakeClosure); isClosure {
804 // For closures, spawn the closure variable (which has bindings already applied).
805 fnVal := fc.value(call.Value)
806 var args []string
807 for _, a := range call.Args {
808 args = append(args, fc.value(a))
809 }
810 fc.e.Line("$rt.goroutine.spawn(async () => %s(%s));", fnVal, strings.Join(args, ", "))
811 } else if callee := call.StaticCallee(); callee != nil {
812 callExpr := fc.emitStaticCall(callee, call.Args)
813 fc.e.Line("$rt.goroutine.spawn(async () => %s);", callExpr)
814 } else {
815 fnVal := fc.value(call.Value)
816 var args []string
817 for _, a := range call.Args {
818 args = append(args, fc.value(a))
819 }
820 fc.e.Line("$rt.goroutine.spawn(async () => %s(%s));", fnVal, strings.Join(args, ", "))
821 }
822 }
823
824 // emitSpawn generates JS for spawn — creates a new isolated browser domain.
825 //
826 // The spawn builtin's SSA signature is func(interface{}, ...interface{}).
827 // Args[0] is MakeInterface(targetFn), Args[1] is a []interface{} slice
828 // created by the SSA builder. We walk the SSA graph to recover the
829 // concrete typed values (same approach as the native backend).
830 func (fc *FunctionCompiler) emitSpawn(callee *ssa.Function, args []ssa.Value) string {
831 if len(args) < 1 {
832 return "/* spawn: no function argument */ undefined"
833 }
834
835 // Check if the first positional argument (fn) is a transport string.
836 // SSA: spawn("pipe", worker, ch) → args[0]=MakeInterface("pipe"), args[1]=variadic
837 transport := "local"
838 hasTransport := false
839 if ts, ok := extractTransportStringJS(args[0]); ok {
840 transport = ts
841 hasTransport = true
842 }
843
844 // Validate transport.
845 switch transport {
846 case "local", "pipe":
847 // OK.
848 default:
849 if len(transport) > 6 && (transport[:6] == "tcp://" || transport[:5] == "ws://" || transport[:6] == "wss://") {
850 return fmt.Sprintf("/* spawn: network transport %q not yet implemented */ undefined", transport)
851 }
852 return fmt.Sprintf("/* spawn: unknown transport %q */ undefined", transport)
853 }
854
855 // Extract all concrete values from the variadic slice.
856 allConcrete := extractSpawnSSAArgsJS(args)
857
858 // When transport is present, the function is allConcrete[0], args are [1:].
859 var fnArg ssa.Value
860 var concreteArgs []ssa.Value
861 if hasTransport {
862 if len(allConcrete) < 1 {
863 return "/* spawn: no function argument after transport string */ undefined"
864 }
865 fnArg = allConcrete[0]
866 concreteArgs = allConcrete[1:]
867 } else {
868 fnArg = args[0]
869 if mi, ok := fnArg.(*ssa.MakeInterface); ok {
870 fnArg = mi.X
871 }
872 concreteArgs = allConcrete
873 }
874
875 // Unwrap MakeInterface on fnArg.
876 if mi, ok := fnArg.(*ssa.MakeInterface); ok {
877 fnArg = mi.X
878 }
879 fnVal := fc.value(fnArg)
880
881 // Find the actual target function to determine its signature.
882 var targetFn *ssa.Function
883 switch v := fnArg.(type) {
884 case *ssa.Function:
885 targetFn = v
886 case *ssa.MakeClosure:
887 if fn, ok := v.Fn.(*ssa.Function); ok {
888 targetFn = fn
889 }
890 }
891
892 // Verify argument count and types match the target function.
893 if targetFn != nil {
894 sig := targetFn.Signature
895 if len(concreteArgs) != sig.Params().Len() {
896 return fmt.Sprintf("/* spawn: %s expects %d arguments, got %d */ undefined",
897 targetFn.Name(), sig.Params().Len(), len(concreteArgs))
898 }
899 for i, arg := range concreteArgs {
900 paramType := sig.Params().At(i).Type()
901 argType := arg.Type()
902 if !types.Identical(argType.Underlying(), paramType.Underlying()) {
903 return fmt.Sprintf("/* spawn: argument %d has type %s, %s expects %s */ undefined",
904 i+1, argType, targetFn.Name(), paramType)
905 }
906 }
907 }
908
909 var jsArgs []string
910 for i, arg := range concreteArgs {
911 jsVal := fc.value(arg)
912 // Emit type-appropriate JS serialization for the spawn boundary.
913 if targetFn != nil && i < targetFn.Signature.Params().Len() {
914 jsVal = fc.emitSpawnArgSerialize(arg, targetFn.Signature.Params().At(i).Type(), jsVal)
915 }
916 jsArgs = append(jsArgs, jsVal)
917 }
918
919 awaitPrefix := ""
920 if fc.needsAsync() {
921 awaitPrefix = "await "
922 }
923
924 if len(jsArgs) > 0 {
925 return fmt.Sprintf("%s$rt.domain.spawn(async () => %s(%s))", awaitPrefix, fnVal, strings.Join(jsArgs, ", "))
926 }
927 return fmt.Sprintf("%s$rt.domain.spawn(async () => %s())", awaitPrefix, fnVal)
928 }
929
930 // emitSpawnArgSerialize wraps a JS value in the appropriate serialization
931 // for crossing a spawn boundary. Slices become Array copies, maps become
932 // Object copies. Value types pass through unchanged.
933 func (fc *FunctionCompiler) emitSpawnArgSerialize(arg ssa.Value, goType types.Type, jsVal string) string {
934 switch goType.Underlying().(type) {
935 case *types.Slice:
936 // Deep copy the slice so child gets independent data.
937 return fmt.Sprintf("[...%s]", jsVal)
938 case *types.Map:
939 // Shallow copy the map so child gets independent entries.
940 return fmt.Sprintf("Object.assign({}, %s)", jsVal)
941 default:
942 return jsVal
943 }
944 }
945
946 // extractSpawnSSAArgsJS recovers concrete values from spawn's variadic slice.
947 // Same logic as extractSpawnSSAArgs in compiler/spawn.go but accessible from
948 // the jsbackend package.
949 // extractTransportStringJS checks if an SSA value is a string constant
950 // (possibly wrapped in MakeInterface) and returns it.
951 func extractTransportStringJS(v ssa.Value) (string, bool) {
952 if mi, ok := v.(*ssa.MakeInterface); ok {
953 v = mi.X
954 }
955 c, ok := v.(*ssa.Const)
956 if !ok {
957 return "", false
958 }
959 if c.Value == nil || c.Value.Kind() != constant.String {
960 return "", false
961 }
962 return constant.StringVal(c.Value), true
963 }
964
965 func extractSpawnSSAArgsJS(args []ssa.Value) []ssa.Value {
966 if len(args) < 2 {
967 return nil
968 }
969
970 variadicArg := args[1]
971 slice, ok := variadicArg.(*ssa.Slice)
972 if !ok {
973 return nil
974 }
975 alloc, ok := slice.X.(*ssa.Alloc)
976 if !ok {
977 return nil
978 }
979 refs := alloc.Referrers()
980 if refs == nil {
981 return nil
982 }
983
984 type indexedValue struct {
985 index int64
986 value ssa.Value
987 }
988 var indexed []indexedValue
989
990 for _, ref := range *refs {
991 ia, ok := ref.(*ssa.IndexAddr)
992 if !ok {
993 continue
994 }
995 iaRefs := ia.Referrers()
996 if iaRefs == nil {
997 continue
998 }
999 for _, iaRef := range *iaRefs {
1000 store, ok := iaRef.(*ssa.Store)
1001 if !ok || store.Addr != ia {
1002 continue
1003 }
1004 idx := int64(0)
1005 if constIdx, ok := ia.Index.(*ssa.Const); ok {
1006 if v, ok := constant.Int64Val(constIdx.Value); ok {
1007 idx = v
1008 }
1009 }
1010 val := store.Val
1011 if mi, ok := val.(*ssa.MakeInterface); ok {
1012 val = mi.X
1013 }
1014 indexed = append(indexed, indexedValue{idx, val})
1015 }
1016 }
1017
1018 for i := 1; i < len(indexed); i++ {
1019 for j := i; j > 0 && indexed[j].index < indexed[j-1].index; j-- {
1020 indexed[j], indexed[j-1] = indexed[j-1], indexed[j]
1021 }
1022 }
1023
1024 result := make([]ssa.Value, len(indexed))
1025 for i, iv := range indexed {
1026 result[i] = iv.value
1027 }
1028 return result
1029 }
1030
1031 // emitSelect generates JS for a select statement.
1032 func (fc *FunctionCompiler) emitSelect(instr *ssa.Select) {
1033 dst := fc.local(instr)
1034
1035 var cases []string
1036 for i, state := range instr.States {
1037 ch := fc.value(state.Chan)
1038 if state.Dir == types.SendOnly {
1039 val := fc.value(state.Send)
1040 cases = append(cases, fmt.Sprintf("{ ch: %s, dir: 'send', value: %s, id: %d }", ch, val, i))
1041 } else {
1042 cases = append(cases, fmt.Sprintf("{ ch: %s, dir: 'recv', id: %d }", ch, i))
1043 }
1044 }
1045
1046 hasDefault := "false"
1047 if !instr.Blocking {
1048 hasDefault = "true"
1049 }
1050
1051 fc.e.Line("%s = await $rt.channel.select([%s], %s);",
1052 dst, strings.Join(cases, ", "), hasDefault)
1053 }
1054
1055 // emitConvert handles type conversions.
1056 func (fc *FunctionCompiler) emitConvert(instr *ssa.Convert) string {
1057 x := fc.value(instr.X)
1058 fromType := instr.X.Type().Underlying()
1059 toType := instr.Type().Underlying()
1060
1061 fromBasic, fromIsBasic := fromType.(*types.Basic)
1062 toBasic, toIsBasic := toType.(*types.Basic)
1063
1064 // String conversions.
1065 if toIsBasic && toBasic.Kind() == types.String {
1066 if fromIsBasic && fromBasic.Info()&types.IsInteger != 0 {
1067 return fmt.Sprintf("String.fromCodePoint(%s)", x)
1068 }
1069 if _, ok := fromType.(*types.Slice); ok {
1070 return fmt.Sprintf("$rt.builtin.bytesToString(%s)", x)
1071 }
1072 }
1073
1074 if fromIsBasic && fromBasic.Kind() == types.String {
1075 if _, ok := toType.(*types.Slice); ok {
1076 return fmt.Sprintf("$rt.builtin.stringToBytes(%s)", x)
1077 }
1078 }
1079
1080 // Numeric conversions.
1081 if fromIsBasic && toIsBasic {
1082 from64 := fromBasic.Kind() == types.Int64 || fromBasic.Kind() == types.Uint64
1083 to64 := toBasic.Kind() == types.Int64 || toBasic.Kind() == types.Uint64
1084
1085 // BigInt (64-bit) → Number (float/smaller int).
1086 if from64 && toBasic.Info()&types.IsFloat != 0 {
1087 return fmt.Sprintf("Number(%s)", x)
1088 }
1089 if from64 && toBasic.Info()&types.IsInteger != 0 && !to64 {
1090 return fc.intTruncate(fmt.Sprintf("Number(%s)", x), toBasic)
1091 }
1092
1093 // Number → BigInt (64-bit).
1094 if !from64 && to64 {
1095 if toBasic.Kind() == types.Uint64 {
1096 return fmt.Sprintf("BigInt.asUintN(64, BigInt(%s))", x)
1097 }
1098 return fmt.Sprintf("BigInt(%s)", x)
1099 }
1100
1101 // float → int (both Number).
1102 if toBasic.Info()&types.IsFloat != 0 && fromBasic.Info()&types.IsInteger != 0 {
1103 if from64 {
1104 return fmt.Sprintf("Number(%s)", x)
1105 }
1106 return x // JS numbers are already float64
1107 }
1108 if toBasic.Info()&types.IsInteger != 0 && fromBasic.Info()&types.IsFloat != 0 {
1109 if to64 {
1110 return fmt.Sprintf("BigInt(Math.trunc(%s))", x)
1111 }
1112 return fmt.Sprintf("Math.trunc(%s)", x)
1113 }
1114 if toBasic.Info()&types.IsInteger != 0 && fromBasic.Info()&types.IsInteger != 0 {
1115 return fc.intTruncate(x, toBasic)
1116 }
1117 }
1118
1119 return x
1120 }
1121
1122 // intTruncate wraps a value to fit a specific integer type.
1123 func (fc *FunctionCompiler) intTruncate(x string, t *types.Basic) string {
1124 switch t.Kind() {
1125 case types.Int8:
1126 return fmt.Sprintf("((%s << 24) >> 24)", x)
1127 case types.Int16:
1128 return fmt.Sprintf("((%s << 16) >> 16)", x)
1129 case types.Int32:
1130 return fmt.Sprintf("(%s | 0)", x)
1131 case types.Uint8:
1132 return fmt.Sprintf("(%s & 0xFF)", x)
1133 case types.Uint16:
1134 return fmt.Sprintf("(%s & 0xFFFF)", x)
1135 case types.Uint32:
1136 return fmt.Sprintf("(%s >>> 0)", x)
1137 case types.Int64:
1138 return fmt.Sprintf("BigInt.asIntN(64, BigInt(%s))", x)
1139 case types.Uint64:
1140 return fmt.Sprintf("BigInt.asUintN(64, BigInt(%s))", x)
1141 default:
1142 return x
1143 }
1144 }
1145
1146 // value returns the JS expression for an SSA value.
1147 func (fc *FunctionCompiler) value(v ssa.Value) string {
1148 if v == nil {
1149 return "undefined"
1150 }
1151
1152 // Constants.
1153 if c, ok := v.(*ssa.Const); ok {
1154 return fc.constValue(c)
1155 }
1156
1157 // Functions.
1158 if fn, ok := v.(*ssa.Function); ok {
1159 return functionJsName(fn)
1160 }
1161
1162 // Globals.
1163 if g, ok := v.(*ssa.Global); ok {
1164 pkg := g.Package()
1165 if pkg != nil && pkg.Pkg.Path() != fc.fn.Package().Pkg.Path() {
1166 return fmt.Sprintf("%s.%s", JsIdentifier(pkg.Pkg.Path()), JsIdentifier(g.Name()))
1167 }
1168 return JsIdentifier(g.Name())
1169 }
1170
1171 // Builtins.
1172 if _, ok := v.(*ssa.Builtin); ok {
1173 return "/* builtin */"
1174 }
1175
1176 // Local variable.
1177 if name, ok := fc.locals[v]; ok {
1178 return name
1179 }
1180
1181 return fmt.Sprintf("/* unknown: %T */", v)
1182 }
1183
1184 // local returns the JS variable name for an SSA value (creating one if needed).
1185 func (fc *FunctionCompiler) local(v ssa.Value) string {
1186 if name, ok := fc.locals[v]; ok {
1187 return name
1188 }
1189 name := fc.freshVar(v.Name())
1190 fc.locals[v] = name
1191 return name
1192 }
1193
1194 // is64bit returns true if the type is int64 or uint64.
1195 func is64bit(t types.Type) bool {
1196 basic, ok := t.Underlying().(*types.Basic)
1197 if !ok {
1198 return false
1199 }
1200 return basic.Kind() == types.Int64 || basic.Kind() == types.Uint64
1201 }
1202
1203 // constValue returns the JS literal for an SSA constant.
1204 func (fc *FunctionCompiler) constValue(c *ssa.Const) string {
1205 if c.Value == nil {
1206 // Typed nil or zero value.
1207 if is64bit(c.Type()) {
1208 return "0n"
1209 }
1210 return fc.pc.typeMapper.ZeroExpr(c.Type())
1211 }
1212
1213 switch c.Value.Kind() {
1214 case constant.Bool:
1215 if constant.BoolVal(c.Value) {
1216 return "true"
1217 }
1218 return "false"
1219 case constant.Int:
1220 // 64-bit integers use BigInt (suffix n) for full precision.
1221 if is64bit(c.Type()) {
1222 if v, exact := constant.Int64Val(c.Value); exact {
1223 return fmt.Sprintf("%dn", v)
1224 }
1225 if v, exact := constant.Uint64Val(c.Value); exact {
1226 return fmt.Sprintf("%dn", v)
1227 }
1228 return c.Value.ExactString() + "n"
1229 }
1230 if v, exact := constant.Int64Val(c.Value); exact {
1231 return fmt.Sprintf("%d", v)
1232 }
1233 if v, exact := constant.Uint64Val(c.Value); exact {
1234 return fmt.Sprintf("%d", v)
1235 }
1236 return c.Value.ExactString()
1237 case constant.Float:
1238 f, _ := constant.Float64Val(c.Value)
1239 return fmt.Sprintf("%g", f)
1240 case constant.String:
1241 return JsString(constant.StringVal(c.Value))
1242 case constant.Complex:
1243 re, _ := constant.Float64Val(constant.Real(c.Value))
1244 im, _ := constant.Float64Val(constant.Imag(c.Value))
1245 return fmt.Sprintf("{ re: %g, im: %g }", re, im)
1246 default:
1247 return "null"
1248 }
1249 }
1250
1251 // intTypeInfo returns (bitSize, isUnsigned) for an integer type.
1252 func intTypeInfo(t types.Type) (int, bool) {
1253 basic, ok := t.Underlying().(*types.Basic)
1254 if !ok {
1255 return 0, false
1256 }
1257 switch basic.Kind() {
1258 case types.Uint8:
1259 return 8, true
1260 case types.Uint16:
1261 return 16, true
1262 case types.Uint32:
1263 return 32, true
1264 case types.Uint64:
1265 return 64, true
1266 case types.Uint, types.Uintptr:
1267 return 32, true // Moxie: int/uint are always 32-bit
1268 case types.Int8:
1269 return 8, false
1270 case types.Int16:
1271 return 16, false
1272 case types.Int32:
1273 return 32, false
1274 case types.Int64:
1275 return 64, false
1276 case types.Int:
1277 return 32, false // Moxie: int/uint are always 32-bit
1278 }
1279 return 0, false
1280 }
1281
1282 // wrapUint wraps an expression to the correct unsigned bit width.
1283 func wrapUint(expr string, bits int) string {
1284 switch bits {
1285 case 8:
1286 return fmt.Sprintf("((%s) & 0xFF)", expr)
1287 case 16:
1288 return fmt.Sprintf("((%s) & 0xFFFF)", expr)
1289 case 32:
1290 return fmt.Sprintf("((%s) >>> 0)", expr)
1291 case 64:
1292 return fmt.Sprintf("BigInt.asUintN(64, %s)", expr)
1293 default:
1294 return expr
1295 }
1296 }
1297
1298 // binOp returns a function that generates the JS binary expression.
1299 func (fc *FunctionCompiler) binOp(op token.Token, t types.Type) func(x, y string) string {
1300 isString := false
1301 if basic, ok := t.Underlying().(*types.Basic); ok {
1302 isString = basic.Info()&types.IsString != 0
1303 }
1304 // Moxie: string=[]byte — a []byte slice is also a string.
1305 if !isString {
1306 if sl, ok := t.Underlying().(*types.Slice); ok {
1307 if basic, ok := sl.Elem().Underlying().(*types.Basic); ok {
1308 isString = basic.Kind() == types.Byte
1309 }
1310 }
1311 }
1312
1313 bits, unsigned := intTypeInfo(t)
1314
1315 // 64-bit integer operations use BigInt for full precision.
1316 if bits == 64 {
1317 wrap := "BigInt.asUintN"
1318 if !unsigned {
1319 wrap = "BigInt.asIntN"
1320 }
1321 switch op {
1322 case token.ADD:
1323 return func(x, y string) string { return fmt.Sprintf("%s(64, %s + %s)", wrap, x, y) }
1324 case token.SUB:
1325 return func(x, y string) string { return fmt.Sprintf("%s(64, %s - %s)", wrap, x, y) }
1326 case token.MUL:
1327 return func(x, y string) string { return fmt.Sprintf("%s(64, %s * %s)", wrap, x, y) }
1328 case token.QUO:
1329 return func(x, y string) string { return fmt.Sprintf("%s(64, %s / %s)", wrap, x, y) }
1330 case token.REM:
1331 return func(x, y string) string { return fmt.Sprintf("(%s %% %s)", x, y) }
1332 case token.AND:
1333 return func(x, y string) string { return fmt.Sprintf("(%s & %s)", x, y) }
1334 case token.OR:
1335 return func(x, y string) string { return fmt.Sprintf("(%s | %s)", x, y) }
1336 case token.XOR:
1337 return func(x, y string) string { return fmt.Sprintf("(%s ^ %s)", x, y) }
1338 case token.SHL:
1339 // Shift amount may be a Number (e.g. uint); coerce to BigInt.
1340 return func(x, y string) string { return fmt.Sprintf("%s(64, %s << BigInt(%s))", wrap, x, y) }
1341 case token.SHR:
1342 if unsigned {
1343 return func(x, y string) string { return fmt.Sprintf("(%s >> BigInt(%s))", x, y) }
1344 }
1345 return func(x, y string) string {
1346 return fmt.Sprintf("BigInt.asIntN(64, %s >> BigInt(%s))", x, y)
1347 }
1348 case token.AND_NOT:
1349 return func(x, y string) string { return fmt.Sprintf("(%s & ~%s)", x, y) }
1350 case token.EQL:
1351 return func(x, y string) string { return fmt.Sprintf("(%s === %s)", x, y) }
1352 case token.NEQ:
1353 return func(x, y string) string { return fmt.Sprintf("(%s !== %s)", x, y) }
1354 case token.LSS:
1355 return func(x, y string) string { return fmt.Sprintf("(%s < %s)", x, y) }
1356 case token.LEQ:
1357 return func(x, y string) string { return fmt.Sprintf("(%s <= %s)", x, y) }
1358 case token.GTR:
1359 return func(x, y string) string { return fmt.Sprintf("(%s > %s)", x, y) }
1360 case token.GEQ:
1361 return func(x, y string) string { return fmt.Sprintf("(%s >= %s)", x, y) }
1362 }
1363 }
1364
1365 switch op {
1366 case token.ADD:
1367 if isString {
1368 return func(x, y string) string { return fmt.Sprintf("(%s + %s)", x, y) }
1369 }
1370 if unsigned && bits <= 32 {
1371 return func(x, y string) string { return wrapUint(fmt.Sprintf("%s + %s", x, y), bits) }
1372 }
1373 return func(x, y string) string { return fmt.Sprintf("(%s + %s)", x, y) }
1374 case token.SUB:
1375 if unsigned && bits <= 32 {
1376 return func(x, y string) string { return wrapUint(fmt.Sprintf("%s - %s", x, y), bits) }
1377 }
1378 return func(x, y string) string { return fmt.Sprintf("(%s - %s)", x, y) }
1379 case token.MUL:
1380 if unsigned && bits == 32 {
1381 // Math.imul gives low 32 bits of integer multiply, then >>> 0 for unsigned.
1382 return func(x, y string) string { return fmt.Sprintf("(Math.imul(%s, %s) >>> 0)", x, y) }
1383 }
1384 if unsigned && bits < 32 {
1385 return func(x, y string) string { return wrapUint(fmt.Sprintf("%s * %s", x, y), bits) }
1386 }
1387 return func(x, y string) string { return fmt.Sprintf("(%s * %s)", x, y) }
1388 case token.QUO:
1389 // Integer division in Go truncates toward zero.
1390 if basic, ok := t.Underlying().(*types.Basic); ok && basic.Info()&types.IsInteger != 0 {
1391 if unsigned && bits <= 32 {
1392 return func(x, y string) string { return wrapUint(fmt.Sprintf("Math.trunc(%s / %s)", x, y), bits) }
1393 }
1394 return func(x, y string) string { return fmt.Sprintf("Math.trunc(%s / %s)", x, y) }
1395 }
1396 return func(x, y string) string { return fmt.Sprintf("(%s / %s)", x, y) }
1397 case token.REM:
1398 if unsigned && bits <= 32 {
1399 return func(x, y string) string { return wrapUint(fmt.Sprintf("%s %% %s", x, y), bits) }
1400 }
1401 return func(x, y string) string { return fmt.Sprintf("(%s %% %s)", x, y) }
1402 case token.AND:
1403 if unsigned && bits == 32 {
1404 return func(x, y string) string { return fmt.Sprintf("((%s & %s) >>> 0)", x, y) }
1405 }
1406 return func(x, y string) string { return fmt.Sprintf("(%s & %s)", x, y) }
1407 case token.OR:
1408 if unsigned && bits == 32 {
1409 return func(x, y string) string { return fmt.Sprintf("((%s | %s) >>> 0)", x, y) }
1410 }
1411 return func(x, y string) string { return fmt.Sprintf("(%s | %s)", x, y) }
1412 case token.XOR:
1413 if unsigned && bits == 32 {
1414 return func(x, y string) string { return fmt.Sprintf("((%s ^ %s) >>> 0)", x, y) }
1415 }
1416 return func(x, y string) string { return fmt.Sprintf("(%s ^ %s)", x, y) }
1417 case token.SHL:
1418 if unsigned && bits <= 32 {
1419 return func(x, y string) string { return wrapUint(fmt.Sprintf("%s << %s", x, y), bits) }
1420 }
1421 return func(x, y string) string { return fmt.Sprintf("(%s << %s)", x, y) }
1422 case token.SHR:
1423 if unsigned {
1424 if bits == 32 {
1425 return func(x, y string) string { return fmt.Sprintf("(%s >>> %s)", x, y) }
1426 }
1427 if bits < 32 {
1428 return func(x, y string) string { return fmt.Sprintf("((%s & %s) >> %s)", x, wrapMask(bits), y) }
1429 }
1430 return func(x, y string) string { return fmt.Sprintf("(%s >>> %s)", x, y) }
1431 }
1432 return func(x, y string) string { return fmt.Sprintf("(%s >> %s)", x, y) }
1433 case token.AND_NOT:
1434 if unsigned && bits == 32 {
1435 return func(x, y string) string { return fmt.Sprintf("((%s & ~%s) >>> 0)", x, y) }
1436 }
1437 return func(x, y string) string { return fmt.Sprintf("(%s & ~%s)", x, y) }
1438 case token.EQL:
1439 if isString {
1440 return func(x, y string) string { return fmt.Sprintf("$rt.builtin.stringEqual(%s, %s)", x, y) }
1441 }
1442 return func(x, y string) string { return fmt.Sprintf("(%s === %s)", x, y) }
1443 case token.NEQ:
1444 if isString {
1445 return func(x, y string) string { return fmt.Sprintf("!$rt.builtin.stringEqual(%s, %s)", x, y) }
1446 }
1447 return func(x, y string) string { return fmt.Sprintf("(%s !== %s)", x, y) }
1448 case token.LSS:
1449 return func(x, y string) string { return fmt.Sprintf("(%s < %s)", x, y) }
1450 case token.LEQ:
1451 return func(x, y string) string { return fmt.Sprintf("(%s <= %s)", x, y) }
1452 case token.GTR:
1453 return func(x, y string) string { return fmt.Sprintf("(%s > %s)", x, y) }
1454 case token.GEQ:
1455 return func(x, y string) string { return fmt.Sprintf("(%s >= %s)", x, y) }
1456 default:
1457 return func(x, y string) string { return fmt.Sprintf("/* TODO: %s */ (%s ? %s)", op, x, y) }
1458 }
1459 }
1460
1461 func wrapMask(bits int) string {
1462 switch bits {
1463 case 8:
1464 return "0xFF"
1465 case 16:
1466 return "0xFFFF"
1467 default:
1468 return "0xFFFFFFFF"
1469 }
1470 }
1471
1472 // needsClone returns true if a Go type needs deep-cloning when stored via pointer.
1473 // Arrays and structs are value types in Go; in JS they're reference types.
1474 func needsClone(t types.Type) bool {
1475 switch t.Underlying().(type) {
1476 case *types.Array:
1477 return true
1478 case *types.Struct:
1479 return true
1480 }
1481 return false
1482 }
1483
1484 // needsAsync checks if this function needs to be declared async.
1485 // Uses the precomputed transitive async set from ProgramCompiler.
1486 func (fc *FunctionCompiler) needsAsync() bool {
1487 return fc.pc.asyncFuncs[fc.fn]
1488 }
1489
1490 // fieldJsName returns the JS property name for a struct field by index.
1491 func fieldJsName(t types.Type, index int) string {
1492 switch t := t.Underlying().(type) {
1493 case *types.Struct:
1494 if index < t.NumFields() {
1495 return JsIdentifier(t.Field(index).Name())
1496 }
1497 }
1498 return fmt.Sprintf("$field_%d", index)
1499 }
1500