Quellcode durchsuchen

ffi: support explicit external link names

Eric Streit vor 2 Tagen
Ursprung
Commit
0cf76c33c4

+ 2 - 0
compiler/run_tests.sh

@@ -169,6 +169,7 @@ expect_run t_chain.mod 42
 expect_run t_goto.mod 42
 expect_run t_goto.mod 42
 expect_fail t_gotobad.mod "undeclared identifier"
 expect_fail t_gotobad.mod "undeclared identifier"
 expect_run t_under.mod 42
 expect_run t_under.mod 42
+expect_run t_extern_alias.mod 42
 expect_run t_with.mod 114
 expect_run t_with.mod 114
 expect_run t_set.mod 131
 expect_run t_set.mod 131
 expect_run t_setchar.mod 77
 expect_run t_setchar.mod 77
@@ -345,6 +346,7 @@ expect_run_files CForeignProg 42 c_foreign.def c_foreign.mod c_foreign_prog.mod
 expect_run_files TCStringProg 42 t_cstring.def t_cstring_prog.mod
 expect_run_files TCStringProg 42 t_cstring.def t_cstring_prog.mod
 expect_run_files TCStringQualProg 42 t_cstring.def t_cstring_qual_prog.mod
 expect_run_files TCStringQualProg 42 t_cstring.def t_cstring_qual_prog.mod
 expect_run_files CStrProg 42 ../runtime/syslib/CStr.def ../runtime/syslib/CStr.mod tests/cstr_test.def tests/cstr_prog.mod
 expect_run_files CStrProg 42 ../runtime/syslib/CStr.def ../runtime/syslib/CStr.mod tests/cstr_test.def tests/cstr_prog.mod
+expect_run_files LinkNameProg 42 linkname.def linkname_prog.mod
 expect_run_files EEnumProg 42 e_enum.def e_enum_re.def e_enum_prog.mod
 expect_run_files EEnumProg 42 e_enum.def e_enum_re.def e_enum_prog.mod
 expect_run_files TClassDefProg 8 t_classdef.def t_classdef_prog.mod
 expect_run_files TClassDefProg 8 t_classdef.def t_classdef_prog.mod
 expect_run_files DQualProg 46 d_basic.def d_basic.mod d_qual_prog.mod
 expect_run_files DQualProg 46 d_basic.def d_basic.mod d_qual_prog.mod

+ 8 - 12
compiler/src/Lower.mod

@@ -1281,6 +1281,7 @@ PROCEDURE AppendArgL (val: ARRAY OF CHAR; ft: SymTab.TypeIndex;
 PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
 PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
                      VAR q: QbeGen.QVal; want: BOOLEAN);
                      VAR q: QbeGen.QVal; want: BOOLEAN);
   VAR nm, pn, cqn, cm: SymTab.Name;
   VAR nm, pn, cqn, cm: SymTab.Name;
+      ln: SymTab.LinkName;
       i, ai: CARDINAL; at, ft, res: SymTab.TypeIndex;
       i, ai: CARDINAL; at, ft, res: SymTab.TypeIndex;
       qa, qt, qe, mg, qres, qb, qn: QbeGen.QVal;
       qa, qt, qe, mg, qres, qb, qn: QbeGen.QVal;
       conv, isV, qual, ind, isC, va, cabi: BOOLEAN;
       conv, isV, qual, ind, isC, va, cabi: BOOLEAN;
@@ -1313,7 +1314,7 @@ PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
          the exact proc entry by the alias's uid. *)
          the exact proc entry by the alias's uid. *)
       pi := FindProcByUid(qualUid);
       pi := FindProcByUid(qualUid);
       IF ProcInfoIdx(pi, uid, res, depth, ext, np) THEN
       IF ProcInfoIdx(pi, uid, res, depth, ext, np) THEN
-        IF ext THEN QbeGen.CopyOp(nm, mg) ELSE QbeGen.Mangled(nm, uid, mg) END;
+        QbeGen.Mangled(nm, uid, mg);
         QbeGen.CallBegin(mg, res, depth, ext)
         QbeGen.CallBegin(mg, res, depth, ext)
       ELSE
       ELSE
         res := SymTab.InvalidType;
         res := SymTab.InvalidType;
@@ -1332,14 +1333,9 @@ PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
         QbeGen.CallBeginInd(qa, res, FALSE)
         QbeGen.CallBeginInd(qa, res, FALSE)
       ELSIF ProcInfo(nm, uid, res, depth, ext, np) THEN
       ELSIF ProcInfo(nm, uid, res, depth, ext, np) THEN
         pi := FindProc(nm);
         pi := FindProc(nm);
-        IF ext THEN
-          (* External callee: bind to its C link name (the procedure's
-             own name); SymTab.ProcLink cannot be consulted after
-             EndUnit. *)
-          QbeGen.CopyOp(nm, mg)
-        ELSE
-          QbeGen.Mangled(nm, uid, mg)
-        END;
+        (* Mangled resolves an explicit external link name by uid, then
+           falls back to the module-qualified internal symbol. *)
+        QbeGen.Mangled(nm, uid, mg);
         QbeGen.CallBegin(mg, res, depth, ext)
         QbeGen.CallBegin(mg, res, depth, ext)
       ELSE
       ELSE
         (* not in Lower's table: a predef C proc (SYSTEM.TRANSFER,
         (* not in Lower's table: a predef C proc (SYSTEM.TRANSFER,
@@ -1348,8 +1344,8 @@ PROCEDURE LowerCall (n: AST.Node; VAR t: SymTab.TypeIndex;
           isC := TRUE;
           isC := TRUE;
           res := SymTab.ProcRes(nm);
           res := SymTab.ProcRes(nm);
           np := SymTab.ProcNPar(nm);
           np := SymTab.ProcNPar(nm);
-          IF SymTab.ProcLink(nm, pn) THEN
-            QbeGen.CallBegin(pn, res, 0, TRUE)
+          IF SymTab.ProcLink(nm, ln) THEN
+            QbeGen.CallBegin(ln, res, 0, TRUE)
           ELSE
           ELSE
             QbeGen.CallBegin(nm, res, 0, FALSE)
             QbeGen.CallBegin(nm, res, 0, FALSE)
           END
           END
@@ -1607,7 +1603,7 @@ PROCEDURE LowerProcAddr (nm: ARRAY OF CHAR; VAR q: QbeGen.QVal);
       END
       END
     END;
     END;
     IF ok THEN
     IF ok THEN
-      IF ext THEN QbeGen.CopyOp(nm, mg) ELSE QbeGen.Mangled(nm, uid, mg) END;
+      QbeGen.Mangled(nm, uid, mg);
       QbeGen.ProcAddr(mg, q)
       QbeGen.ProcAddr(mg, q)
     ELSE QbeGen.CopyOp("0", q)
     ELSE QbeGen.CopyOp("0", q)
     END
     END

+ 31 - 6
compiler/src/M2.atg

@@ -207,6 +207,20 @@ PROCEDURE CopyNm (src: ARRAY OF CHAR; VAR dst: SymTab.Name);
     IF i <= HIGH(dst) THEN dst[i] := CHR(0) END
     IF i <= HIGH(dst) THEN dst[i] := CHR(0) END
   END CopyNm;
   END CopyNm;
 
 
+PROCEDURE CLink (tok: ARRAY OF CHAR; VAR link: SymTab.LinkName);
+(* Strip the quotes from a string token and copy it as a C link name. *)
+  VAR n, i, j: CARDINAL;
+  BEGIN
+    n := SymTab.StrLen(tok);
+    link[0] := CHR(0);
+    IF n < 2 THEN RETURN END;
+    i := 1; j := 0;
+    WHILE (i < n - 1) AND (j < HIGH(link)) DO
+      link[j] := tok[i]; INC(i); INC(j)
+    END;
+    IF j <= HIGH(link) THEN link[j] := CHR(0) END
+  END CLink;
+
 PROCEDURE WrapExcept (body, handler: AST.Node): AST.Node;
 PROCEDURE WrapExcept (body, handler: AST.Node): AST.Node;
 (* M1: a body with an EXCEPT part is represented as one node so Lower
 (* M1: a body with an EXCEPT part is represented as one node so Lower
    can add handler codegen in one place later.  The handler is retained
    can add handler codegen in one place later.  The handler is retained
@@ -862,6 +876,8 @@ PRODUCTIONS
      table; imports materialize exported names. *)
      table; imports materialize exported names. *)
   DefUnit                               (. VAR m1, m2, pn: SymTab.Name;
   DefUnit                               (. VAR m1, m2, pn: SymTab.Name;
                                              g: SymTab.Name;
                                              g: SymTab.Name;
+                                             ln, raw: SymTab.LinkName;
+                                             procIsExt: BOOLEAN;
                                              astSeq, astTail, astProc: AST.Node; .)
                                              astSeq, astTail, astProc: AST.Node; .)
     =                                   (. refN := 0; refSeen := FALSE; .)
     =                                   (. refN := 0; refSeen := FALSE; .)
       "DEFINITION" "MODULE"
       "DEFINITION" "MODULE"
@@ -892,13 +908,18 @@ PRODUCTIONS
         GetIdent<pn> { "," GetIdent<pn> } ";" ]
         GetIdent<pn> { "," GetIdent<pn> } ";" ]
                                         (. astSeq := AST.NoNode;
                                         (. astSeq := AST.NoNode;
                                            astTail := AST.NoNode; .)
                                            astTail := AST.NoNode; .)
-      { ConstBlock                      (. AstAppend(AST.NkDeclSeq,
+      {                                 (. ln[0] := CHR(0);
+                                           procIsExt := FALSE; .)
+        ( ConstBlock                    (. AstAppend(AST.NkDeclSeq,
                                              astSeq, astTail, astDecl); .)
                                              astSeq, astTail, astDecl); .)
       | TypeBlock<TRUE>                 (. AstAppend(AST.NkDeclSeq,
       | TypeBlock<TRUE>                 (. AstAppend(AST.NkDeclSeq,
                                              astSeq, astTail, astDecl); .)
                                              astSeq, astTail, astDecl); .)
       | VarBlock                        (. AstAppend(AST.NkDeclSeq,
       | VarBlock                        (. AstAppend(AST.NkDeclSeq,
                                              astSeq, astTail, astDecl); .)
                                              astSeq, astTail, astDecl); .)
       | ProcHeading<pn, SymTab.InvalidType> ";"
       | ProcHeading<pn, SymTab.InvalidType> ";"
+        [ "EXTERNAL"                    (. procIsExt := TRUE; .)
+          [ string                      (. LexString(raw);
+                                             CLink(raw, ln); .) ] ";" ]
                                         (. (* a heading only: record it so
                                         (. (* a heading only: record it so
                                               Lower can replay it *)
                                               Lower can replay it *)
                                            astProc := AST.MakeNode(AST.NkProcDecl);
                                            astProc := AST.MakeNode(AST.NkProcDecl);
@@ -908,13 +929,14 @@ PRODUCTIONS
                                            AstAppend(AST.NkDeclSeq,
                                            AstAppend(AST.NkDeclSeq,
                                              astSeq, astTail, astProc);
                                              astSeq, astTail, astProc);
                                            Lower.NoteProcNode(astProc);
                                            Lower.NoteProcNode(astProc);
-                                           IF foreignUnit THEN
+                                           IF foreignUnit OR procIsExt THEN
                                              Lower.MarkProcExternal;
                                              Lower.MarkProcExternal;
-                                             SymTab.MarkExternal("")
+                                             SymTab.MarkExternal(ln)
                                            END;
                                            END;
                                            Lower.ScopeLeave;
                                            Lower.ScopeLeave;
                                            SymTab.CloseProc;
                                            SymTab.CloseProc;
-                                           QbeGen.AbortFunc; .) }
+                                           QbeGen.AbortFunc; .) )
+      }
                                         (. astUnit := AST.MakeNode(AST.NkUnit);
                                         (. astUnit := AST.MakeNode(AST.NkUnit);
                                            AST.SetChild(astUnit, 0,
                                            AST.SetChild(astUnit, 0,
                                              AST.MakeLeaf(AST.NkIdent, m1));
                                              AST.MakeLeaf(AST.NkIdent, m1));
@@ -2316,6 +2338,7 @@ PRODUCTIONS
      static link gives them their parent's frame. Methods keep
      static link gives them their parent's frame. Methods keep
      parse-now/230-later. *)
      parse-now/230-later. *)
   ProcDecl                              (. VAR pn: SymTab.Name;
   ProcDecl                              (. VAR pn: SymTab.Name;
+                                             ln, raw: SymTab.LinkName;
                                              saveFun: SymTab.Name;
                                              saveFun: SymTab.Name;
                                              astNode: AST.Node; .)
                                              astNode: AST.Node; .)
     = ProcHeading<pn, SymTab.InvalidType> ";"
     = ProcHeading<pn, SymTab.InvalidType> ";"
@@ -2330,11 +2353,13 @@ PRODUCTIONS
                                            SymTab.MarkFwd;
                                            SymTab.MarkFwd;
                                            SymTab.CloseProc;
                                            SymTab.CloseProc;
                                            QbeGen.AbortFunc; .)
                                            QbeGen.AbortFunc; .)
-      | "EXTERNAL"                      (. AST.SetOp(astNode, 2);
+      | "EXTERNAL"                      (. ln[0] := CHR(0); .)
+        [ string                        (. LexString(raw); CLink(raw, ln); .) ]
+                                        (. AST.SetOp(astNode, 2);
                                            astDecl := astNode;
                                            astDecl := astNode;
                                            Lower.MarkProcExternal;
                                            Lower.MarkProcExternal;
                                            Lower.ScopeLeave;
                                            Lower.ScopeLeave;
-                                           SymTab.MarkExternal("");
+                                           SymTab.MarkExternal(ln);
                                            SymTab.CloseProc;
                                            SymTab.CloseProc;
                                            QbeGen.AbortFunc; .)
                                            QbeGen.AbortFunc; .)
       |                                 
       |                                 

+ 1 - 1
compiler/src/QbeGen.def

@@ -12,7 +12,7 @@ DEFINITION MODULE QbeGen;
    $main return its value as the process exit code, else return 0. *)
    $main return its value as the process exit code, else return 0. *)
 
 
 TYPE
 TYPE
-  QVal = ARRAY [0 .. 63] OF CHAR;
+  QVal = ARRAY [0 .. 255] OF CHAR;
 
 
 PROCEDURE OpenModule (name: ARRAY OF CHAR);
 PROCEDURE OpenModule (name: ARRAY OF CHAR);
 (* Opens gen_ssa/<name>.ssa, resets counters. *)
 (* Opens gen_ssa/<name>.ssa, resets counters. *)

+ 8 - 3
compiler/src/QbeGen.mod

@@ -1098,9 +1098,14 @@ PROCEDURE Mangled (pname: ARRAY OF CHAR; uid: CARDINAL; VAR q: QVal);
    so objects compiled in separate sessions agree.  Nested procedures
    so objects compiled in separate sessions agree.  Nested procedures
    are local to their module object; they keep a deterministic uid
    are local to their module object; they keep a deterministic uid
    suffix, prefixed by the module to avoid cross-object collisions. *)
    suffix, prefixed by the module to avoid cross-object collisions. *)
-  VAR link, base, mod : SymTab.Name;
+  VAR link : SymTab.LinkName;
+      base, mod, tmod, tname : SymTab.Name;
       depth : CARDINAL; classIdx : INTEGER; num : QVal;
       depth : CARDINAL; classIdx : INTEGER; num : QVal;
   BEGIN
   BEGIN
+    IF SymTab.ProcLinkByUidName(uid, pname, link) THEN
+      Cpy(q, link);
+      RETURN
+    END;
     IF SymTab.ProcLink(pname, link) THEN
     IF SymTab.ProcLink(pname, link) THEN
       Cpy(q, link);
       Cpy(q, link);
       RETURN
       RETURN
@@ -1121,8 +1126,8 @@ PROCEDURE Mangled (pname: ARRAY OF CHAR; uid: CARDINAL; VAR q: QVal);
       App(q, base);
       App(q, base);
       classIdx := SymTab.ProcClassByUid(uid);
       classIdx := SymTab.ProcClassByUid(uid);
       IF classIdx # SymTab.InvalidType THEN
       IF classIdx # SymTab.InvalidType THEN
-        IF SymTab.TypeStableName(classIdx, link, base) THEN
-          App(q, "_"); App(q, link); App(q, "__"); App(q, base)
+        IF SymTab.TypeStableName(classIdx, tmod, tname) THEN
+          App(q, "_"); App(q, tmod); App(q, "__"); App(q, tname)
         ELSE
         ELSE
           App(q, "_c");
           App(q, "_c");
           IntStr(classIdx, num);
           IntStr(classIdx, num);

+ 10 - 1
compiler/src/SymTab.def

@@ -62,6 +62,7 @@ CONST
 
 
 TYPE
 TYPE
   Name = ARRAY [0 .. 63] OF CHAR;
   Name = ARRAY [0 .. 63] OF CHAR;
+  LinkName = ARRAY [0 .. 255] OF CHAR;  (* external C symbol *)
   TypeIndex = INTEGER;
   TypeIndex = INTEGER;
   BoundsArr = ARRAY [0 .. 7] OF INTEGER;
   BoundsArr = ARRAY [0 .. 7] OF INTEGER;
 (* Per-dimension bounds for array index lists (grammar locals). *)
 (* Per-dimension bounds for array index lists (grammar locals). *)
@@ -187,7 +188,15 @@ PROCEDURE DeclareCProc (name: ARRAY OF CHAR): BOOLEAN;
    result): FROM libc IMPORT. *)
    result): FROM libc IMPORT. *)
 PROCEDURE Varargs (name: ARRAY OF CHAR): BOOLEAN;
 PROCEDURE Varargs (name: ARRAY OF CHAR): BOOLEAN;
 (* TRUE when the named procedure is a C-varargs intrinsic. *)
 (* TRUE when the named procedure is a C-varargs intrinsic. *)
-PROCEDURE ProcLink (name: ARRAY OF CHAR; VAR link: Name): BOOLEAN;
+PROCEDURE ProcLink (name: ARRAY OF CHAR; VAR link: LinkName): BOOLEAN;
+
+PROCEDURE ProcLinkByUid (uid: CARDINAL; VAR link: LinkName): BOOLEAN;
+(* External link name by procedure uid; useful for qualified aliases. *)
+
+PROCEDURE ProcLinkByUidName (uid: CARDINAL; name: ARRAY OF CHAR;
+                             VAR link: LinkName): BOOLEAN;
+(* As ProcLinkByUid, but only when uid's base name matches name.  This
+   disambiguates uid 0 and aliases from unrelated predef procedures. *)
 (* External-procedure query/link lookup (link name of the named
 (* External-procedure query/link lookup (link name of the named
    visible procedure). *)
    visible procedure). *)
 
 

+ 36 - 2
compiler/src/SymTab.mod

@@ -52,7 +52,7 @@ TYPE
     vslot : INTEGER;    (* KindProc VIRTUAL: vtable slot (-1 = none) *)
     vslot : INTEGER;    (* KindProc VIRTUAL: vtable slot (-1 = none) *)
     hasBody : BOOLEAN;  (* KindProc: a body was resumed/emitted *)
     hasBody : BOOLEAN;  (* KindProc: a body was resumed/emitted *)
     ext   : BOOLEAN;    (* KindProc: EXTERNAL (no body emitted) *)
     ext   : BOOLEAN;    (* KindProc: EXTERNAL (no body emitted) *)
-    link  : Name;       (* KindProc: external link (C) name *)
+    link  : LinkName;   (* KindProc: external link (C) name *)
     varargs : BOOLEAN;  (* KindProc: C varargs (FROM libc IMPORT) *)
     varargs : BOOLEAN;  (* KindProc: C varargs (FROM libc IMPORT) *)
     sym   : Name;       (* base symbol name (differs from name for
     sym   : Name;       (* base symbol name (differs from name for
                            qualified aliases) *)
                            qualified aliases) *)
@@ -109,6 +109,7 @@ VAR
   uidMod  : ARRAY [0 .. 1023] OF Name;  (* uid -> defining module *)
   uidMod  : ARRAY [0 .. 1023] OF Name;  (* uid -> defining module *)
   uidDepth : ARRAY [0 .. 1023] OF CARDINAL; (* uid -> lexical depth *)
   uidDepth : ARRAY [0 .. 1023] OF CARDINAL; (* uid -> lexical depth *)
   uidClass : ARRAY [0 .. 1023] OF TypeIndex; (* uid -> class method owner *)
   uidClass : ARRAY [0 .. 1023] OF TypeIndex; (* uid -> class method owner *)
+  uidLink : ARRAY [0 .. 1023] OF LinkName;  (* uid -> external C link *)
   nDefer : CARDINAL;
   nDefer : CARDINAL;
   deferProc : ARRAY [0 .. MaxDefer - 1] OF SymPtr;
   deferProc : ARRAY [0 .. MaxDefer - 1] OF SymPtr;
   deferScope : ARRAY [0 .. MaxDefer - 1] OF ScopePtr;
   deferScope : ARRAY [0 .. MaxDefer - 1] OF ScopePtr;
@@ -2605,6 +2606,9 @@ PROCEDURE DeclareCProc (name: ARRAY OF CHAR): BOOLEAN;
       uidDepth[node^.uid] := nProc;
       uidDepth[node^.uid] := nProc;
       uidClass[node^.uid] := InvalidType
       uidClass[node^.uid] := InvalidType
     END;
     END;
+    IF node^.uid <= HIGH(uidLink) THEN
+      Assign(uidLink[node^.uid], name)
+    END;
     node^.fdep := nProc;
     node^.fdep := nProc;
     node^.vslot := -1;
     node^.vslot := -1;
     node^.hasBody := TRUE;
     node^.hasBody := TRUE;
@@ -2766,10 +2770,13 @@ PROCEDURE MarkExternal (link: ARRAY OF CHAR);
     curProc^.ext := TRUE;
     curProc^.ext := TRUE;
     IF link[0] = CHR(0) THEN Assign(curProc^.link, curProc^.name)
     IF link[0] = CHR(0) THEN Assign(curProc^.link, curProc^.name)
     ELSE Assign(curProc^.link, link)
     ELSE Assign(curProc^.link, link)
+    END;
+    IF curProc^.uid <= HIGH(uidLink) THEN
+      Assign(uidLink[curProc^.uid], curProc^.link)
     END
     END
   END MarkExternal;
   END MarkExternal;
 
 
-PROCEDURE ProcLink (name: ARRAY OF CHAR; VAR link: Name): BOOLEAN;
+PROCEDURE ProcLink (name: ARRAY OF CHAR; VAR link: LinkName): BOOLEAN;
   VAR node: SymPtr;
   VAR node: SymPtr;
   BEGIN
   BEGIN
     node := Find(name);
     node := Find(name);
@@ -2779,6 +2786,30 @@ PROCEDURE ProcLink (name: ARRAY OF CHAR; VAR link: Name): BOOLEAN;
     RETURN TRUE
     RETURN TRUE
   END ProcLink;
   END ProcLink;
 
 
+PROCEDURE ProcLinkByUid (uid: CARDINAL; VAR link: LinkName): BOOLEAN;
+  BEGIN
+    link[0] := CHR(0);
+    IF (uid > HIGH(uidLink)) OR (uidLink[uid][0] = CHR(0)) THEN
+      RETURN FALSE
+    END;
+    Assign(link, uidLink[uid]);
+    RETURN TRUE
+  END ProcLinkByUid;
+
+PROCEDURE ProcLinkByUidName (uid: CARDINAL; name: ARRAY OF CHAR;
+                             VAR link: LinkName): BOOLEAN;
+  BEGIN
+    link[0] := CHR(0);
+    IF (uid > HIGH(uidLink)) OR (uidLink[uid][0] = CHR(0)) THEN
+      RETURN FALSE
+    END;
+    IF (uidBase[uid][0] = CHR(0)) OR NOT Equal(uidBase[uid], name) THEN
+      RETURN FALSE
+    END;
+    Assign(link, uidLink[uid]);
+    RETURN TRUE
+  END ProcLinkByUidName;
+
 PROCEDURE CloseProc;
 PROCEDURE CloseProc;
   BEGIN
   BEGIN
     IF nProc > 0 THEN DEC(nProc) END;
     IF nProc > 0 THEN DEC(nProc) END;
@@ -3793,6 +3824,9 @@ PROCEDURE PredefCProc (name, cname: ARRAY OF CHAR; res: TypeIndex): SymPtr;
     node^.fsnap := FALSE;
     node^.fsnap := FALSE;
     Assign(node^.link, cname);
     Assign(node^.link, cname);
     node^.uid := nextUid; INC(nextUid);
     node^.uid := nextUid; INC(nextUid);
+    IF node^.uid <= HIGH(uidLink) THEN
+      Assign(uidLink[node^.uid], cname)
+    END;
     node^.fdep := 0;
     node^.fdep := 0;
     node^.vslot := -1;
     node^.vslot := -1;
     node^.hasBody := TRUE;
     node^.hasBody := TRUE;

+ 9 - 0
compiler/tests/linkname.def

@@ -0,0 +1,9 @@
+DEFINITION MODULE FOR "C" LinkNameTest;
+(* Short Modula-2 name, long explicit C link name. *)
+
+EXPORT UNQUALIFIED long_sum;
+
+PROCEDURE long_sum (a, b : INTEGER) : INTEGER;
+  EXTERNAL "m2_this_is_a_very_long_c_symbol_name_for_link_alias_test_0123456789";
+
+END LinkNameTest.

+ 15 - 0
compiler/tests/linkname_prog.mod

@@ -0,0 +1,15 @@
+MODULE LinkNameProg;
+(* The FOR "C" definition maps long_sum to a C symbol longer than the
+   old 64-character V3 name limit.  Expected exit code: 42. *)
+
+FROM LinkNameTest IMPORT long_sum;
+
+VAR ExitCode : INTEGER;
+
+BEGIN
+  IF long_sum(19, 23) = 42 THEN
+    ExitCode := 42
+  ELSE
+    ExitCode := 1
+  END
+END LinkNameProg.

+ 15 - 0
compiler/tests/t_extern_alias.mod

@@ -0,0 +1,15 @@
+MODULE TExternAlias;
+(* Implementation-side EXTERNAL alias with a long C symbol. *)
+
+PROCEDURE long_sum (a, b : INTEGER) : INTEGER;
+  EXTERNAL "m2_this_is_a_very_long_c_symbol_name_for_link_alias_test_0123456789";
+
+VAR ExitCode : INTEGER;
+
+BEGIN
+  IF long_sum(20, 22) = 42 THEN
+    ExitCode := 42
+  ELSE
+    ExitCode := 1
+  END
+END TExternAlias.

+ 3 - 0
docs/features.md

@@ -89,6 +89,9 @@ Legend: ✅ done · 🔄 partial · ⏸ not started / deferred.
   `CString` results and `VAR CString` (`char **`) outputs cover C
   `CString` results and `VAR CString` (`char **`) outputs cover C
   string returns and out-parameters; writable C buffers can still be
   string returns and out-parameters; writable C buffers can still be
   declared as `VAR ARRAY OF CHAR`.
   declared as `VAR ARRAY OF CHAR`.
+- ✅ `EXTERNAL "c_symbol"` link aliases, in implementations and
+  `FOR "C"` definitions; external link names use a 256-character
+  `SymTab.LinkName`, so short Modula-2 names can bind long C symbols.
 - ✅ Set builtins `INCL`/`EXCL` and `SHIFT`/`ROTATE`; `SET OF <enum>`
 - ✅ Set builtins `INCL`/`EXCL` and `SHIFT`/`ROTATE`; `SET OF <enum>`
   and `SET OF <char-range>`; bounded sets up to 1,000,000 bits;
   and `SET OF <char-range>`; bounded sets up to 1,000,000 bits;
   `SET OF CARDINAL` and `SET OF INTEGER` use a sorted range-list
   `SET OF CARDINAL` and `SET OF INTEGER` use a sorted range-list

+ 7 - 0
runtime/syslib/shim.c

@@ -422,6 +422,13 @@ long m2_cstr_fill(char *buf, long cap)
     return (long)n;
     return (long)n;
 }
 }
 
 
+/* Deliberately longer than the old 64-character V3 symbol limit. */
+long m2_this_is_a_very_long_c_symbol_name_for_link_alias_test_0123456789(
+    long a, long b)
+{
+    return a + b;
+}
+
 /* UString length: the codepoint count is the descriptor's header. */
 /* UString length: the codepoint count is the descriptor's header. */
 long m2ustrlen(long *s)
 long m2ustrlen(long *s)
 {
 {