Skip to content

@linkname: a second name for @dllname, and one C symbol for many declarations - #366

Merged
ASDAlexander77 merged 11 commits into
mainfrom
linkname-decorator
Sep 26, 2026
Merged

ASDAlexander77 merged 11 commits into
mainfrom
linkname-decorator

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

PR 1 of the plan in docs/superpowers/specs/2026-09-25-tsbindgen-linkname-design.md. It adds @linkname("sym") as a second spelling of @dllname("sym"). It also fixes two bugs that stop a C function being bound under another TS name. tsbindgen (the stacked PR) needs these fixes for its --namespace output.

Changes

@linkname is an alias of @dllname. Both spellings map to the same DLL_NAME attribute in iterateDecorators, so everything that already reads @dllname accepts @linkname, including __decls, which still prints @dllname(...). Using both on one declaration with different symbols is an error. Using both with the same symbol is allowed.

Several declarations of one C symbol now bind it together. The rename used to happen in LLVM (ExportFixPass). When the name was already taken, setName quietly made it unique:

declare function strlen(s: string): index;
@linkname("strlen") declare function cStrLen(s: string): index;
@dllname("strlen")  declare function cStrLen2(s: string): index;
  • Before: the IR had @strlen, @strlen.1 and @strlen.2. The JIT failed with Symbols not found: [ strlen.1, strlen.2 ] and the exe with LNK2019: unresolved external symbol strlen.1.
  • After: all three calls go to strlen, both linked and under the JIT (declare_linkname_shared_symbol.ts).

The rename now happens in MLIR (LowerToLLVM.cpp, beside the existing global rename). Declarations of one symbol are merged. Everything else is an error instead of a silent .1:

  • two different function types;
  • two definitions;
  • a target that is a global variable;
  • a rename to a name another rename just gave up (either order).

The error cases are the test-compile-linkname-* tests.

export declare no longer marks the C function dllexport. A declaration without a body stops getting the export attribute. Whether it is re-printed into __decls is unchanged. test-compile-linkname-declare-not-exported checks the IR has no declare dllexport.

  • An exe that export declares strlen still writes a .lib/.exp. Those come from the module's own __decls/__tsmm dllexport globals, which are exported on purpose.
  • strlen itself was not exported before this change and is not exported after it. The spec said the exe "re-exports strlen" and blamed it for the .lib; that was wrong, and the spec has been corrected.

Existing bug found while testing: a function carrying @dllname/@linkname was private. Under --opt, inlining plus SymbolDCE deleted it before it could be renamed. That is why @dllname definitions went missing under --opt. Such functions are now public.

Also on this branch

  • Spec and plan documents: the design spec, and the PR 1 and PR 2 plans (docs/superpowers/).
  • 601ea68, JIT: --emit=jit now runs a main(argc, argv) that takes parameters and returns a result, and defaults to -O3. Test: 00main_argc_argv.ts, 00main_number_result.ts. Two AOT problems are still open:
    • argv.length reads envp;
    • a main returning number doesn't give a proper exit code.
  • 0dbb472: the VS Code template's CMake minimum version (3.21).

Tests

Full Release suite on Windows: all pass, with one test disabled.

The disabled test is test-compile-shared-export-import-declare-linkname. Importing a -shared module's export declare binding of a C runtime function crashes: the importer looks strlen up in the loaded tslang libraries, which never contain it. A build from before this branch crashes the same way with @dllname, so this is an existing bug: #368. Its static-link twin, test-compile-export-import-declare-linkname, passes and guards the same path.

Known gaps

  • Error messages name the already-renamed symbol ('strlen') rather than the TS name.
  • A global whose @linkname target is a function gives an internal "failed to legalize" error instead of a clear one. @dllname already behaved this way.

🤖 Generated with Claude Code

ASDAlexander77 and others added 11 commits September 25, 2026 21:20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r lookup, C string open issue

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es and enhancing function declaration handling
--emit=jit refused any main with parameters or a result ("expected 'main' to
have 0 inputs and 0 results"), because the JIT called the entry as void(*)().
It now calls a generated `i32 __tslang_jit_main(i32, ptr)` thunk, built in
LLVM IR after the optimizer, that adapts to main's lowered signature:
argc as an integer or number, argv as a string[] (data, length) or a
Ref<string> (the C char ** itself), and the result (void, integer or number)
as the process exit code.

- LowerToAffineLoops: the JIT gate is now a shape check for
  main(argc?: i32 | number, argv?: string[] | Ref<string>): void | i32 | number
- tslang: trailing positionals are program arguments for --emit=jit
  (argv[0] is the input file, '--' before dash-prefixed ones); an error in
  every other mode, as a second positional was before
- tslang: --emit=jit turns --opt on (level 3 by default) unless --opt is given
  explicitly, --opt_level=0, or --di (--opt strips the debug info)
- tests: 00main_argc_argv (JIT and compiled), 00main_number_result (JIT only:
  a compiled number-typed main gets an int argc and returns no exit code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LLVM's setName made a taken name unique, so a second declaration of
strlen called strlen.1 and linked to nothing.

A function with @dllname/@Linkname is now public: it defines a symbol
that is bound by that name, and as a private function --opt inlined
its callers and SymbolDCE erased it before the rename, leaving the
declaration of that name with no definition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cross-module test of an imported `export declare` binding is
registered DISABLED: importing one crashes (null symbol lookup), as it
did with @dllname before this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tically

A function renamed to a name another @dllname/@Linkname function is
renamed away from was accepted or rejected depending on which came
first in the file. Every vacated name is now recorded before the
renames run.

The static-link multi-file pairs for @Linkname and for an imported
`export declare` binding were passing but not registered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 7477fa9 into main Sep 26, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the linkname-decorator branch September 26, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant