Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions compiler/lowering.go
Original file line number Diff line number Diff line change
Expand Up @@ -2452,6 +2452,8 @@ func lazyPackageVars(semPkg *semanticPackage) map[types.Object]bool {
}
funcRefs := packageVarsReferencedFromOtherFileTopLevelCalls(semPkg, declFiles)
lazy := make(map[types.Object]bool)
otherFile := make(map[types.Object]bool)
var inits []packageVarInit
for idx, file := range semPkg.source.Syntax {
sourcePath := sourceFilePath(semPkg, idx, file)
for _, decl := range file.Decls {
Expand Down Expand Up @@ -2483,11 +2485,13 @@ func lazyPackageVars(semPkg *semanticPackage) map[types.Object]bool {
if valueIdx < len(valueSpec.Values) &&
initializerReferencesOtherFileObject(semPkg, declFiles, sourcePath, valueSpec.Values[valueIdx]) {
lazy[obj] = true
otherFile[obj] = true
continue
}
if valueIdx < len(valueSpec.Values) &&
initializerCallsFunctionReferencingOtherFileObject(semPkg, declFiles, sourcePath, valueSpec.Values[valueIdx]) {
lazy[obj] = true
otherFile[obj] = true
continue
}
if valueIdx < len(valueSpec.Values) &&
Expand All @@ -2498,20 +2502,90 @@ func lazyPackageVars(semPkg *semanticPackage) map[types.Object]bool {
if valueIdx >= len(valueSpec.Values) &&
zeroValueReferencesOtherFileObject(semPkg, declFiles, sourcePath, obj.Type()) {
lazy[obj] = true
otherFile[obj] = true
continue
}
if funcRefs[obj] &&
(valueIdx >= len(valueSpec.Values) ||
!initializerMayHaveRuntimeEffects(lowerFileContext{semPkg: semPkg}, valueSpec.Values[valueIdx])) {
lazy[obj] = true
continue
}

// The other initializers may read variables that need other files (see markLazyReaders).
if valueIdx < len(valueSpec.Values) && len(valueSpec.Names) == len(valueSpec.Values) {
inits = append(inits, packageVarInit{obj: obj, expr: valueSpec.Values[valueIdx]})
}
}
}
}
}
markLazyReaders(semPkg, lazy, otherFile, inits)
return lazy
}

// packageVarInit is a package variable and its initializer.
type packageVarInit struct {
obj *types.Var
expr ast.Expr
}

// markLazyReaders marks as lazy the variables of the given initializers that
// read a variable of otherFile, the lazy variables whose initializers need
// declarations of other files, and adds them to otherFile, until no more are
// marked. Reading such a variable runs its initializer, which may need a
// declaration, such as a class, of a file that is not evaluated yet, when the
// file of the reader is evaluated first. Initializers with runtime effects
// stay eager, so they still run at package initialization, as do those of
// blank variables, which are never read.
func markLazyReaders(semPkg *semanticPackage, lazy, otherFile map[types.Object]bool, inits []packageVarInit) {
// Each pass marks the readers of the variables marked so far.
ctx := lowerFileContext{semPkg: semPkg}
for changed := true; changed; {
changed = false
for _, init := range inits {
// Skip the variables that are lazy, never read, or not readers.
if lazy[init.obj] || init.obj.Name() == "_" || !initializerReadsVar(semPkg, otherFile, init.expr) {
continue
}

// Mark a reader whose initializer has no runtime effects.
if initializerMayHaveRuntimeEffects(ctx, init.expr) {
continue
}
lazy[init.obj] = true
otherFile[init.obj] = true
changed = true
}
}
}

// initializerReadsVar returns whether expr reads a package variable of vars
// outside of function literals, which run later.
func initializerReadsVar(semPkg *semanticPackage, vars map[types.Object]bool, expr ast.Expr) bool {
reads := false
ast.Inspect(expr, func(node ast.Node) bool {
// Stop after a read, and skip function literals, which do not run
// when the variable is initialized.
if reads {
return false
}
if _, ok := node.(*ast.FuncLit); ok {
return false
}

// An identifier may use a variable of vars.
ident, ok := node.(*ast.Ident)
if !ok {
return true
}
obj, ok := semPkg.source.TypesInfo.Uses[ident].(*types.Var)
reads = ok && vars[obj]
return !reads
})
return reads
}

func packageVarsReferencedFromOtherFileTopLevelCalls(
semPkg *semanticPackage,
declFiles map[types.Object]string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Generated file based on __goscript_package_init.go
// Updated when compliance tests are re-run, DO NOT EDIT!

import * as __goscript_file0 from "./c.gs.ts"

let initialized = false

export async function __goscript_package_init(): globalThis.Promise<void> {
if (initialized) {
return
}
initialized = true
__goscript_file0.__goscript_init_cTable()
__goscript_file0.__goscript_init_cCopy()
}
4 changes: 4 additions & 0 deletions tests/tests/cross_file_struct_typeinfo_zero/b.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ type bValue struct {
func makeB() bValue {
return bValue{}
}

func countC() int {
return len(cCopy)
}
5 changes: 5 additions & 0 deletions tests/tests/cross_file_struct_typeinfo_zero/b.gs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import * as $ from "@goscript/builtin/index.js"
import * as __goscript_a from "./a.gs.ts"
import * as __goscript_c from "./c.gs.ts"

export class bValue {
public declare inner: __goscript_a.aValue
Expand Down Expand Up @@ -33,3 +34,7 @@ export class bValue {
export function makeB(): bValue {
return new bValue(true)
}

export function countC(): number {
return $.sliceLen(__goscript_c.__goscript_get_cCopy())
}
8 changes: 8 additions & 0 deletions tests/tests/cross_file_struct_typeinfo_zero/c.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

// cTable is initialized lazily, as its values have a type of another file,
// and so must be cCopy, which reads it: b.go, which declares that type,
// uses cCopy, so its file is evaluated after this one.
var cTable = []bValue{{}, {}}

var cCopy = cTable
45 changes: 45 additions & 0 deletions tests/tests/cross_file_struct_typeinfo_zero/c.gs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Generated file based on c.go
// Updated when compliance tests are re-run, DO NOT EDIT!

import * as $ from "@goscript/builtin/index.js"
import * as __goscript_a from "./a.gs.ts"
import * as __goscript_b from "./b.gs.ts"
import "./a.gs.ts"

export var cTable: $.Slice<__goscript_b.bValue>

export function __goscript_init_cTable(): void {
if (((cTable) as any) === undefined) {
cTable = $.arrayToSlice<__goscript_b.bValue>([new __goscript_b.bValue(true), new __goscript_b.bValue(true)])
}
}

export function __goscript_get_cTable(): $.Slice<__goscript_b.bValue> {
if (((cTable) as any) === undefined) {
__goscript_init_cTable()
}
return cTable
}

export function __goscript_set_cTable(__goscriptValue: $.Slice<__goscript_b.bValue>): void {
cTable = __goscriptValue
}

export var cCopy: $.Slice<__goscript_b.bValue>

export function __goscript_init_cCopy(): void {
if (((cCopy) as any) === undefined) {
cCopy = __goscript_get_cTable()
}
}

export function __goscript_get_cCopy(): $.Slice<__goscript_b.bValue> {
if (((cCopy) as any) === undefined) {
__goscript_init_cCopy()
}
return cCopy
}

export function __goscript_set_cCopy(__goscriptValue: $.Slice<__goscript_b.bValue>): void {
cCopy = __goscriptValue
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ func main() {
a := makeA()
b := makeB()
println("ok:", a.next == nil, b.inner.next == nil)
println("table:", countC(), cCopy[1].inner.next == nil)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
import * as $ from "@goscript/builtin/index.js"
import * as __goscript_a from "./a.gs.ts"
import * as __goscript_b from "./b.gs.ts"
import * as __goscript_c from "./c.gs.ts"
import * as __goscript_package_init from "./__goscript_package_init.gs.ts"

export async function main(): globalThis.Promise<void> {
let a = __goscript_a.makeA()
let b = __goscript_b.makeB()
await $.println("ok:", a.next == null, b.inner.next == null)
await $.println("table:", __goscript_b.countC(), $.plainSliceIndex(__goscript_c.__goscript_get_cCopy()!, 1).inner.next == null)
}

await __goscript_package_init.__goscript_package_init()

if ($.isMainScript(import.meta)) {
await main()
}
1 change: 1 addition & 0 deletions tests/tests/cross_file_struct_typeinfo_zero/expected.log
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ok: true true
table: 2 true
3 changes: 3 additions & 0 deletions tests/tests/cross_file_struct_typeinfo_zero/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export { aValue } from "./a.gs.ts"
export { bValue } from "./b.gs.ts"
import "./c.gs.ts"
import { __goscript_package_init } from "./__goscript_package_init.gs.ts"
await __goscript_package_init()
2 changes: 2 additions & 0 deletions tests/tests/cross_file_struct_typeinfo_zero/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
},
"extends": "../../../tests/tsconfig.base.json",
"include": [
"__goscript_package_init.gs.ts",
"a.gs.ts",
"b.gs.ts",
"c.gs.ts",
"cross_file_struct_typeinfo_zero.gs.ts",
"index.ts"
]
Expand Down
Loading