Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into modules
Browse files Browse the repository at this point in the history
  • Loading branch information
mstoykov committed Jun 7, 2024
2 parents 08f562e + ccbae20 commit c733ab4
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.16.x, 1.x]
go-version: [1.20.x, 1.x]
os: [ubuntu-latest]
arch: ["", "386"]
fail-fast: false
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ performance.

This project was largely inspired by [otto](https://github.com/robertkrimen/otto).

Minimum required Go version is 1.16.
The minimum required Go version is 1.20.

Features
--------
Expand Down
41 changes: 27 additions & 14 deletions compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ type srcMapItem struct {
// This representation is not linked to a runtime in any way and can be used concurrently.
// It is always preferable to use a Program over a string when running code as it skips the compilation step.
type Program struct {
code []instruction
values []Value
code []instruction

funcName unistring.String
src *file.File
Expand All @@ -94,6 +93,8 @@ type compiler struct {
ctxVM *vm // VM in which an eval() code is compiled

codeScratchpad []instruction

stringCache map[unistring.String]Value
}

func (c *compiler) getScriptOrModule() interface{} {
Expand Down Expand Up @@ -407,6 +408,29 @@ func (c *compiler) popScope() {
c.scope = c.scope.outer
}

func (c *compiler) emitLiteralString(s String) {
key := s.string()
if c.stringCache == nil {
c.stringCache = make(map[unistring.String]Value)
}
internVal := c.stringCache[key]
if internVal == nil {
c.stringCache[key] = s
internVal = s
}

c.emit(loadVal{internVal})
}

func (c *compiler) emitLiteralValue(v Value) {
if s, ok := v.(String); ok {
c.emitLiteralString(s)
return
}

c.emit(loadVal{v})
}

func newCompiler() *compiler {
c := &compiler{
p: &Program{},
Expand All @@ -417,23 +441,11 @@ func newCompiler() *compiler {
return c
}

func (p *Program) defineLiteralValue(val Value) uint32 {
for idx, v := range p.values {
if v.SameAs(val) {
return uint32(idx)
}
}
idx := uint32(len(p.values))
p.values = append(p.values, val)
return idx
}

func (p *Program) dumpCode(logger func(format string, args ...interface{})) {
p._dumpCode("", logger)
}

func (p *Program) _dumpCode(indent string, logger func(format string, args ...interface{})) {
logger("values: %+v", p.values)
dumpInitFields := func(initFields *Program) {
i := indent + ">"
logger("%s ---- init_fields:", i)
Expand Down Expand Up @@ -1202,6 +1214,7 @@ func (c *compiler) compile(in *ast.Program, strict, inGlobal bool, evalVm *vm) {
}

scope.finaliseVarAlloc(0)
c.stringCache = nil
}

func (c *compiler) compileAmbiguousImport(name unistring.String) {
Expand Down
26 changes: 13 additions & 13 deletions compiler_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ type compiledDynamicImport struct {
func (e *defaultDeleteExpr) emitGetter(putOnStack bool) {
e.expr.emitGetter(false)
if putOnStack {
e.c.emit(loadVal(e.c.p.defineLiteralValue(valueTrue)))
e.c.emitLiteralValue(valueTrue)
}
}

Expand Down Expand Up @@ -380,7 +380,7 @@ func (e *baseCompiledExpr) addSrcMap() {
func (e *constantExpr) emitGetter(putOnStack bool) {
if putOnStack {
e.addSrcMap()
e.c.emit(loadVal(e.c.p.defineLiteralValue(e.val)))
e.c.emitLiteralValue(e.val)
}
}

Expand Down Expand Up @@ -1268,7 +1268,7 @@ func (e *compiledAssignExpr) emitGetter(putOnStack bool) {

func (e *compiledLiteral) emitGetter(putOnStack bool) {
if putOnStack {
e.c.emit(loadVal(e.c.p.defineLiteralValue(e.val)))
e.c.emitLiteralValue(e.val)
}
}

Expand All @@ -1279,31 +1279,31 @@ func (e *compiledLiteral) constant() bool {
func (e *compiledTemplateLiteral) emitGetter(putOnStack bool) {
if e.tag == nil {
if len(e.elements) == 0 {
e.c.emit(loadVal(e.c.p.defineLiteralValue(stringEmpty)))
e.c.emitLiteralString(stringEmpty)
} else {
tail := e.elements[len(e.elements)-1].Parsed
if len(e.elements) == 1 {
e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(tail))))
e.c.emitLiteralString(stringValueFromRaw(tail))
} else {
stringCount := 0
if head := e.elements[0].Parsed; head != "" {
e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(head))))
e.c.emitLiteralString(stringValueFromRaw(head))
stringCount++
}
e.expressions[0].emitGetter(true)
e.c.emit(_toString{})
stringCount++
for i := 1; i < len(e.elements)-1; i++ {
if elt := e.elements[i].Parsed; elt != "" {
e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(elt))))
e.c.emitLiteralString(stringValueFromRaw(elt))
stringCount++
}
e.expressions[i].emitGetter(true)
e.c.emit(_toString{})
stringCount++
}
if tail != "" {
e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(tail))))
e.c.emitLiteralString(stringValueFromRaw(tail))
stringCount++
}
e.c.emit(concatStrings(stringCount))
Expand Down Expand Up @@ -2478,7 +2478,7 @@ func (c *compiler) emitThrow(v Value) {
c.emit(loadDynamic(t))
msg := o.self.getStr("message", nil)
if msg != nil {
c.emit(loadVal(c.p.defineLiteralValue(msg)))
c.emitLiteralValue(msg)
c.emit(_new(1))
} else {
c.emit(_new(0))
Expand All @@ -2495,7 +2495,7 @@ func (c *compiler) emitConst(expr compiledExpr, putOnStack bool) {
v, ex := c.evalConst(expr)
if ex == nil {
if putOnStack {
c.emit(loadVal(c.p.defineLiteralValue(v)))
c.emitLiteralValue(v)
}
} else {
c.emitThrow(ex.val)
Expand Down Expand Up @@ -2661,7 +2661,7 @@ func (e *compiledLogicalOr) emitGetter(putOnStack bool) {
e.c.emitExpr(e.right, putOnStack)
} else {
if putOnStack {
e.c.emit(loadVal(e.c.p.defineLiteralValue(v)))
e.c.emitLiteralValue(v)
}
}
} else {
Expand Down Expand Up @@ -2702,7 +2702,7 @@ func (e *compiledCoalesce) emitGetter(putOnStack bool) {
e.c.emitExpr(e.right, putOnStack)
} else {
if putOnStack {
e.c.emit(loadVal(e.c.p.defineLiteralValue(v)))
e.c.emitLiteralValue(v)
}
}
} else {
Expand Down Expand Up @@ -2742,7 +2742,7 @@ func (e *compiledLogicalAnd) emitGetter(putOnStack bool) {
if e.left.constant() {
if v, ex := e.c.evalConst(e.left); ex == nil {
if !v.ToBoolean() {
e.c.emit(loadVal(e.c.p.defineLiteralValue(v)))
e.c.emitLiteralValue(v)
} else {
e.c.emitExpr(e.right, putOnStack)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler_stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ func (c *compiler) compileReturnStatement(v *ast.ReturnStatement) {
for b := c.block; b != nil; b = b.outer {
switch b.typ {
case blockTry:
c.emit(leaveTry{})
c.emit(saveResult, leaveTry{}, loadResult)
case blockLoopEnum:
c.emit(enumPopClose)
}
Expand Down
47 changes: 45 additions & 2 deletions compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"os"
"sync"
"testing"
"unsafe"

"github.com/dop251/goja/unistring"
)

const TESTLIB = `
Expand Down Expand Up @@ -1117,6 +1120,20 @@ func TestReturnOutOfTryNested(t *testing.T) {
testScript(SCRIPT, intToValue(1), t)
}

func TestReturnOutOfTryWithFinally(t *testing.T) {
const SCRIPT = `
function test() {
try {
return 'Hello, world!';
} finally {
const dummy = 'unexpected';
}
}
test();
`
testScript(SCRIPT, asciiString("Hello, world!"), t)
}

func TestContinueLoop(t *testing.T) {
const SCRIPT = `
function A() {
Expand Down Expand Up @@ -4683,9 +4700,15 @@ func TestBadObjectKey(t *testing.T) {

func TestConstantFolding(t *testing.T) {
testValues := func(prg *Program, result Value, t *testing.T) {
if len(prg.values) != 1 || !prg.values[0].SameAs(result) {
values := make(map[unistring.String]struct{})
for _, ins := range prg.code {
if lv, ok := ins.(loadVal); ok {
values[lv.v.string()] = struct{}{}
}
}
if len(values) != 1 {
prg.dumpCode(t.Logf)
t.Fatalf("values: %v", prg.values)
t.Fatalf("values: %v", values)
}
}
f := func(src string, result Value, t *testing.T) {
Expand Down Expand Up @@ -4729,6 +4752,26 @@ func TestConstantFolding(t *testing.T) {
})
}

func TestStringInterning(t *testing.T) {
const SCRIPT = `
const str1 = "Test";
function f() {
return "Test";
}
[str1, f()];
`
vm := New()
res, err := vm.RunString(SCRIPT)
if err != nil {
t.Fatal(err)
}
str1 := res.(*Object).Get("0").String()
str2 := res.(*Object).Get("1").String()
if unsafe.StringData(str1) != unsafe.StringData(str2) {
t.Fatal("not interned")
}
}

func TestAssignBeforeInit(t *testing.T) {
const SCRIPT = `
assert.throws(ReferenceError, () => {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/dop251/goja

go 1.16
go 1.20

require (
github.com/dlclark/regexp2 v1.7.0
Expand Down
23 changes: 20 additions & 3 deletions runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2325,15 +2325,32 @@ func TestStacktraceLocationThrowFromCatch(t *testing.T) {
t.Fatal("Expected error")
}
stack := err.(*Exception).stack
if len(stack) != 2 {
if len(stack) != 3 {
t.Fatalf("Unexpected stack len: %v", stack)
}
if frame := stack[0]; frame.funcName != "main" || frame.pc != 29 {
if frame := stack[0]; frame.funcName != "f2" || frame.pc != 2 {
t.Fatalf("Unexpected stack frame 0: %#v", frame)
}
if frame := stack[1]; frame.funcName != "" || frame.pc != 7 {
if frame := stack[1]; frame.funcName != "main" || frame.pc != 17 {
t.Fatalf("Unexpected stack frame 1: %#v", frame)
}
if frame := stack[2]; frame.funcName != "" || frame.pc != 7 {
t.Fatalf("Unexpected stack frame 2: %#v", frame)
}
}

func TestErrorStackRethrow(t *testing.T) {
const SCRIPT = `
function f(e) {
throw e;
}
try {
f(new Error());
} catch(e) {
assertStack(e, [["test.js", "", 6, 5]]);
}
`
testScriptWithTestLibX(SCRIPT, _undefined, t)
}

func TestStacktraceLocationThrowFromGo(t *testing.T) {
Expand Down
Loading

0 comments on commit c733ab4

Please sign in to comment.