diff --git a/.github/workflows/debug-tcc.yaml b/.github/workflows/debug-tcc.yaml index 25900c46..840b26e4 100644 --- a/.github/workflows/debug-tcc.yaml +++ b/.github/workflows/debug-tcc.yaml @@ -1,5 +1,8 @@ name: debug-tcc -on: workflow_dispatch +on: + push: + branches: [ci-debug-tcc] + workflow_dispatch: jobs: debug: diff --git a/compiler/codegen.js b/compiler/codegen.js index 12c6cbf6..02074db4 100644 --- a/compiler/codegen.js +++ b/compiler/codegen.js @@ -27,6 +27,7 @@ const numValue = x => x[N_TYPE] === T.f64 ? x : x[N_TYPE] === T.i32 ? Convert(T.f64, x, CONVERT_SIGNED) : x[N_TYPE] === T.u32 || x[N_TYPE] === T.ptr ? Convert(T.f64, x) : JvNum(x); + const isRawNum = x => x[N_TYPE] === T.f64 || x[N_TYPE] === T.i32 || x[N_TYPE] === T.u32 || x[N_TYPE] === T.ptr; const intLiteralValue = x => { if (x[N_KIND] === K.Const && (x[N_TYPE] === T.jsval || x[N_TYPE] === T.f64) && Number.isInteger(x[N_A])) return x[N_A]; @@ -69,6 +70,7 @@ const rawAddType = (left, right) => { if (right[N_TYPE] === T.ptr || right[N_TYPE] === T.u32) return right[N_TYPE]; return T.i32; }; + const coerceValue = (v, type) => type === T.jsval ? (v[N_TYPE] === T.jsval ? v : valNumber(v)) : v[N_TYPE] === type ? v : type === T.ptr ? (isRawInt(v) ? Convert(T.ptr, v, 0) : JvPtr(v)) @@ -102,11 +104,13 @@ const CLASS_FIELD_INIT_MARKER = Symbol('class field init'); // evaluate for effects, discard value const exprStmt = (scope, node) => { if (node == null) return; + const isIRNode = Array.isArray(node) && typeof node[N_KIND] === 'number' && typeof node[N_TYPE] === 'number' && typeof node[N_FX] === 'number'; if (!isIRNode && Array.isArray(node)) { for (const x of node) exprStmt(scope, x); return; } + if (node[N_TYPE] === T.none) { stmt(scope, node); return; } if ((node[N_FX] & (FX.call | FX.writeMem | FX.writeLocal)) !== 0) stmt(scope, node); }; @@ -115,7 +119,13 @@ const collect = (scope, fn) => { const list = []; const m = mark(scope); scope.blockStack.push(list); - try { fn(); } finally { scope.blockStack.pop(); } + + try { + fn(); + } finally { + scope.blockStack.pop(); + } + release(scope, m); return list; }; @@ -128,6 +138,7 @@ const tmp = (scope, type = T.jsval, init = null) => { name = `#${type}${scope.tmpCount[type] = (scope.tmpCount[type] ?? 0) + 1}`; scope.locals[name] = { type, temp: true }; } + scope.tmpBusy.push({ name, type }); const node = Local(name, type); if (init != null) stmt(scope, Assign(node, init)); @@ -148,6 +159,7 @@ const release = (scope, m) => { const local = (scope, name, type = T.jsval) => { const l = scope.locals[name]; if (l) return Local(name, l.type); + scope.locals[name] = { type }; return Local(name, type); }; @@ -155,10 +167,13 @@ const local = (scope, name, type = T.jsval) => { // make expr safe to reference twice: consts/locals as-is, the rest into a temp const reuse = (scope, expr) => { const k = expr[N_KIND]; - if (k === K.Const || k === K.Local || k === K.JvConst || k === K.Global || k === K.DataRef) return expr; + if (k === K.Const || k === K.Local || k === K.JvConst || k === K.Global || k === K.DataRef) + return expr; + if (k === K.Box && (expr[N_A][N_KIND] === K.Const || expr[N_A][N_KIND] === K.DataRef) && expr[N_B][N_KIND] === K.Const) return expr; + return tmp(scope, expr[N_TYPE], expr); }; @@ -166,6 +181,7 @@ const reuse = (scope, expr) => { const reuseNamed = (scope, expr) => { const k = expr[N_KIND]; if (k === K.Local || k === K.Global) return expr; + return tmp(scope, expr[N_TYPE], expr); }; @@ -197,6 +213,7 @@ const dataSeg = (key, bytes) => { const cached = dataCache.get(key); if (cached !== undefined) return cached; } + const id = data.push(bytes) - 1; if (key != null) dataCache.set(key, id); return id; @@ -211,12 +228,11 @@ const hasFuncWithName = name => let doNotMarkFuncRef = false; -// an escaping coroutine func can be called dynamically: mark its generator/promise type -// used so prototype dispatch (.next/.then) is included, mirroring generateCall's direct path const coroTypeUsed = func => { if (!func.generator && !func.async) return; - usedTypes.add(func.async ? (func.generator ? TYPES.__porffor_asyncgenerator : TYPES.promise) : TYPES.__porffor_generator); - if (func.async && func.generator) usedTypes.add(TYPES.promise); + + if (func.async) usedTypes.add(TYPES.promise); + if (func.generator) usedTypes.add(func.async ? TYPES.__porffor_asyncgenerator : TYPES.__porffor_generator); }; // function value with no env: one static [fnIdx][0] record per func @@ -224,36 +240,25 @@ const funcRef = (func, markReferenced = true) => { if (markReferenced && !doNotMarkFuncRef) func.referenced = true; func.indirect = true; coroTypeUsed(func); + func.generate?.(); return valOf(dataRef(`#funcrec:${func.index}`, [ ...i32Bytes(func.index), ...i32Bytes(0) ]), TYPES.function); }; -const closureAwareFunc = func => - Prefs.closures && - !func.internal && - !func.noClosureEnv && - !func.topLevel && - !!(func.closureCaptures || func.closureCapturesThis || func.closurePassThrough); - -const hasClosureOwnEnv = scope => - !!(scope.closureOwnLocals || scope.closureOwnThis); - -const hasClosureCaptures = func => +const closureAwareFunc = func => Prefs.closures && !func.internal && !func.noClosureEnv && !func.topLevel && !!(func.closureCaptures || func.closureCapturesThis || func.closurePassThrough); - -const directCallOnlyFunctionBinding = (scope, kind, name, node, func) => - (kind === 'const' || (kind === 'var' && (node._directCallMinStart ?? -1) > (node._declarator?.end ?? node.end ?? node.start ?? 0))) && - !func.selfAware && - (node._directCallRefs ?? 0) > 0 && - (node._valueRefs ?? 0) === 0 && - (node._writes ?? 0) === 0 && - !scope.closureOwnLocals?.[name]; +const hasClosureOwnEnv = scope => !!(scope.closureOwnLocals || scope.closureOwnThis); +const hasClosureCaptures = func => !!(func.closureCaptures || func.closureCapturesThis || func.closurePassThrough); const directCallOnlyRefs = node => (node?._directCallRefs ?? 0) > 0 && (node?._valueRefs ?? 0) === 0 && (node?._writes ?? 0) === 0; +const directCallOnlyFunctionBinding = (scope, kind, name, node, func) => + (kind === 'const' || (kind === 'var' && (node._directCallMinStart ?? -1) > (node._declarator?.end ?? node.end ?? node.start ?? 0))) && + !func.selfAware && directCallOnlyRefs(node) && !scope.closureOwnLocals?.[name]; + const getPerIterationClosureCaptureNames = func => { if (!func) return []; if (func.perIterationClosureCaptureNames) return func.perIterationClosureCaptureNames; @@ -271,7 +276,6 @@ const getClosureSnapshotCaptureNames = func => { if (func.closureSnapshotCaptureNames) return func.closureSnapshotCaptureNames; const out = new Set(getPerIterationClosureCaptureNames(func)); - for (const name in func.closureCaptures ?? {}) { const capture = func.closureCaptures[name]; const capturedFunc = capture?.node?._func; @@ -285,9 +289,7 @@ const getClosureSnapshotCaptureNames = func => { return func.closureSnapshotCaptureNames = [ ...out ]; }; -const hasClosureSnapshotEnv = scope => - getClosureSnapshotCaptureNames(scope).length > 0; - +const hasClosureSnapshotEnv = scope => getClosureSnapshotCaptureNames(scope).length > 0; const closureOwnerMatches = (scope, owner) => scope?.ast === owner || scope?.ast?._closureSource === owner || @@ -296,13 +298,13 @@ const closureOwnerMatches = (scope, owner) => scope?.ast?.type === 'Program' && scope.ast._variables === owner._variables ); - const closureOwnerDepth = (scope, owner) => { let depth = 0; let cursor = scope; if (hasClosureOwnEnv(cursor) || hasClosureSnapshotEnv(cursor)) { if (closureOwnerMatches(cursor, owner)) return 0; + cursor = cursor.parentFunc; depth = 1; } else { @@ -375,7 +377,6 @@ const currentClosureEnv = scope => { } if (scope.closureAware) return valOf(Local('#env', T.ptr), TYPES.object); - return valUndefined(); }; @@ -407,6 +408,7 @@ const makeClosureRecord = (scope, func, markReferenced = true) => { value: { type: 'Identifier', name } })) })); + exprStmt(scope, builtinCall(scope, '__Porffor_object_setPrototype', [ snapshot, parent ])); env = snapshot; } @@ -427,6 +429,7 @@ const makeFreshFuncRecord = (scope, func, markReferenced = true) => { func.indirect = true; coroTypeUsed(func); func.generate?.(); + const rec = reuse(scope, Alloc(Const(T.i32, 8), TYPES.function)); stmt(scope, Store('u32', rec, 0, Const(T.u32, func.index))); stmt(scope, Store('u32', rec, 4, Const(T.u32, 0))); @@ -592,7 +595,7 @@ const generate = (scope, decl, name = undefined, valueUnused = false) => { case 'TaggedTemplateExpression': return generateTaggedTemplate(scope, decl); - case 'ExportNamedDeclaration': + case 'ExportNamedDeclaration': { if (!decl.declaration) { for (const spec of decl.specifiers ?? []) { const local = spec.local?.name; @@ -611,18 +614,17 @@ const generate = (scope, decl, name = undefined, valueUnused = false) => { return valUndefined(); } - { - const funcsBefore = new Set(funcs); - generate(scope, decl.declaration); - for (const x of funcs) { - if (funcsBefore.has(x) || x.internal) continue; - x.export = true; - x.exportName ??= x.name; - x.generate?.(); - } + const funcsBefore = new Set(funcs); + generate(scope, decl.declaration); + for (const x of funcs) { + if (funcsBefore.has(x) || x.internal) continue; + x.export = true; + x.exportName ??= x.name; + x.generate?.(); } return valUndefined(); + } case 'TSAsExpression': { const value = generate(scope, decl.expression); @@ -633,10 +635,14 @@ const generate = (scope, decl, name = undefined, valueUnused = false) => { if (type.irType === T.ptr) return JvPtr(value); return Convert(type.irType, numValue(value), type.irType === T.i32 ? CONVERT_SIGNED : 0); } - if (type.type === TYPES.bigint) return Box(numValue(value), Const(T.i32, TYPES.bigint)); + + if (type.type === TYPES.bigint) + return Box(numValue(value), Const(T.i32, TYPES.bigint)); if (type.type != null && value[N_TYPE] !== T.jsval) return Box(type.type === TYPES.number ? numValue(value) : value, Const(T.i32, type.type)); - if (type.type > 5) return Box(JvPtr(value), Const(T.i32, type.type)); + if (type.type > 5) + return Box(JvPtr(value), Const(T.i32, type.type)); + return value; }