From 1d4a304202620eee0c9d87fc80e2c867a4c76267 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 4 Jun 2020 14:13:14 -0400
Subject: [PATCH] FIXME: WIP on extended-asm support (v2)

---
 gcc/jit/docs/topics/compatibility.rst |   2 +
 gcc/jit/docs/topics/functions.rst     |   3 +
 gcc/jit/jit-common.h                  |   1 +
 gcc/jit/jit-playback.c                | 108 +++++++++
 gcc/jit/jit-playback.h                |  25 +++
 gcc/jit/jit-recording.c               | 272 +++++++++++++++++++++++
 gcc/jit/jit-recording.h               | 162 ++++++++++++++
 gcc/jit/libgccjit.c                   | 109 +++++++++
 gcc/jit/libgccjit.h                   |  70 ++++++
 gcc/jit/libgccjit.map                 |  14 +-
 gcc/testsuite/jit.dg/test-asm.c       | 303 ++++++++++++++++++++++++++
 11 files changed, 1068 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/jit.dg/test-asm.c

diff --git a/gcc/jit/docs/topics/compatibility.rst b/gcc/jit/docs/topics/compatibility.rst
index bb3387fa583..ca49eb61fe3 100644
--- a/gcc/jit/docs/topics/compatibility.rst
+++ b/gcc/jit/docs/topics/compatibility.rst
@@ -219,3 +219,5 @@ entrypoints:
   * :func:`gcc_jit_version_minor`
 
   * :func:`gcc_jit_version_patchlevel`
+
+.. TODO: notes on new assembler-related entrypoints
diff --git a/gcc/jit/docs/topics/functions.rst b/gcc/jit/docs/topics/functions.rst
index eb40d64010e..7a522d6a2d5 100644
--- a/gcc/jit/docs/topics/functions.rst
+++ b/gcc/jit/docs/topics/functions.rst
@@ -458,3 +458,6 @@ Statements
        :start-after: /* Quote from here in docs/topics/functions.rst.  */
        :end-before: /* Quote up to here in docs/topics/functions.rst.  */
        :language: c
+
+.. TODO: docs for inline assembler, maybe split out into a separate
+   topic-related file
diff --git a/gcc/jit/jit-common.h b/gcc/jit/jit-common.h
index 4570bd2d717..983957edbc1 100644
--- a/gcc/jit/jit-common.h
+++ b/gcc/jit/jit-common.h
@@ -131,6 +131,7 @@ namespace recording {
       class base_call;
       class function_pointer;
     class statement;
+      class extended_asm;
     class case_;
 
   /* End of recording types. */
diff --git a/gcc/jit/jit-playback.c b/gcc/jit/jit-playback.c
index 0fddf04da87..96d511324ca 100644
--- a/gcc/jit/jit-playback.c
+++ b/gcc/jit/jit-playback.c
@@ -39,6 +39,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "opt-suggestions.h"
 #include "gcc.h"
 #include "diagnostic.h"
+#include "stmt.h"
 
 #include <pthread.h>
 
@@ -1802,6 +1803,113 @@ add_switch (location *loc,
   add_stmt (switch_stmt);
 }
 
+/* FIXME.  */
+
+static tree
+build_string (const char *str)
+{
+  if (str)
+    return ::build_string (strlen (str), str);
+  else
+    return NULL_TREE;
+}
+
+/* FIXME.  */
+/* Compare with c_parser_asm_operands.  */
+
+static tree
+build_operand_chain (const auto_vec <playback::asm_operand> *operands)
+{
+  tree result = NULL_TREE;
+  unsigned i;
+  playback::asm_operand *asm_op;
+  FOR_EACH_VEC_ELT (*operands, i, asm_op)
+    {
+      tree name = build_string (asm_op->m_asm_symbolic_name);
+      tree str = build_string (asm_op->m_constraint);
+      tree value = asm_op->m_expr;
+      result = chainon (result,
+			build_tree_list (build_tree_list (name, str),
+					 value));
+    }
+  return result;
+}
+
+/* FIXME.  */
+/* Compare with c_parser_asm_clobbers.  */
+
+static tree
+build_clobbers (const auto_vec <const char *> *clobbers)
+{
+  tree list = NULL_TREE;
+  unsigned i;
+  const char *clobber;
+  FOR_EACH_VEC_ELT (*clobbers, i, clobber)
+    {
+      tree str = build_string (clobber);
+      list = tree_cons (NULL_TREE, str, list);
+    }
+  return list;
+}
+
+/* FIXME.  */
+/* Compare with c_parser_asm_goto_operands.  */
+
+static tree
+build_goto_operands (const auto_vec <playback::block *> *blocks)
+{
+  tree list = NULL_TREE;
+  unsigned i;
+  playback::block *b;
+  FOR_EACH_VEC_ELT (*blocks, i, b)
+    {
+      tree label = b->as_label_decl ();
+      tree name = build_string (IDENTIFIER_POINTER (DECL_NAME (label)));
+      TREE_USED (label) = 1;
+      list = tree_cons (name, label, list);
+    }
+  return nreverse (list);
+}
+
+/* FIXME. */
+
+void
+playback::block::add_extended_asm (location *loc,
+				   const char *asm_template,
+				   bool is_volatile,
+				   bool is_inline,
+				   const auto_vec <asm_operand> *outputs,
+				   const auto_vec <asm_operand> *inputs,
+				   const auto_vec <const char *> *clobbers,
+				   const auto_vec <block *> *goto_blocks)
+{
+  /* See c/c-parser.c c_parser_asm_statement.  */
+  /* c/c-typeck.c has build_asm_expr.  */
+  tree t_string = build_string (asm_template);
+  tree t_outputs = build_operand_chain (outputs);
+  tree t_inputs = build_operand_chain (inputs);
+  tree t_clobbers = build_clobbers (clobbers);
+  tree t_labels = build_goto_operands (goto_blocks);
+  t_string
+    = resolve_asm_operand_names (t_string, t_outputs, t_inputs, t_labels);
+  tree asm_stmt
+    = build5 (ASM_EXPR, void_type_node,
+	      t_string, t_outputs, t_inputs, t_clobbers, t_labels);
+
+  /* asm statements without outputs, including simple ones, are treated
+     as volatile.  */
+#if 0
+  ASM_INPUT_P (asm_stmt) = simple;
+#endif
+  ASM_VOLATILE_P (asm_stmt) = (outputs->length () == 0);
+  ASM_INLINE_P (asm_stmt) = is_inline;
+  if (is_volatile)
+    ASM_VOLATILE_P (asm_stmt) = 1;
+  if (loc)
+    set_tree_location (asm_stmt, loc);
+  add_stmt (asm_stmt);
+}
+
 /* Constructor for gcc::jit::playback::block.  */
 
 playback::block::
diff --git a/gcc/jit/jit-playback.h b/gcc/jit/jit-playback.h
index f9b3e675368..88039f4e549 100644
--- a/gcc/jit/jit-playback.h
+++ b/gcc/jit/jit-playback.h
@@ -497,6 +497,21 @@ struct case_
   block *m_dest_block;
 };
 
+struct asm_operand
+{
+  asm_operand (const char *asm_symbolic_name,
+	       const char *constraint,
+	       tree expr)
+  : m_asm_symbolic_name (asm_symbolic_name),
+    m_constraint (constraint),
+    m_expr (expr)
+  {}
+
+  const char *m_asm_symbolic_name;
+  const char *m_constraint;
+  tree m_expr;
+};
+
 class block : public wrapper
 {
 public:
@@ -546,6 +561,16 @@ public:
 	      block *default_block,
 	      const auto_vec <case_> *cases);
 
+  void
+  add_extended_asm (location *loc,
+		    const char *asm_template,
+		    bool is_volatile,
+		    bool is_inline,
+		    const auto_vec <asm_operand> *outputs,
+		    const auto_vec <asm_operand> *inputs,
+		    const auto_vec <const char *> *clobbers,
+		    const auto_vec <block *> *goto_blocks);
+
 private:
   void
   set_tree_location (tree t, location *loc)
diff --git a/gcc/jit/jit-recording.c b/gcc/jit/jit-recording.c
index b73cd76a0a0..3da86ff8f8f 100644
--- a/gcc/jit/jit-recording.c
+++ b/gcc/jit/jit-recording.c
@@ -4125,6 +4125,19 @@ recording::block::add_comment (recording::location *loc,
   return result;
 }
 
+/* FIXME.  */
+
+recording::extended_asm *
+recording::block::add_extended_asm (location *loc,
+				    const char *asm_template)
+{
+  extended_asm *result
+    = new extended_asm (this, loc, new_string (asm_template));
+  m_ctxt->record (result);
+  m_statements.safe_push (result);
+  return result;
+}
+
 /* Create a recording::end_with_conditional instance and add it to
    the block's context's list of mementos, and to the block's
    list of statements.
@@ -4207,6 +4220,25 @@ recording::block::end_with_switch (recording::location *loc,
   return result;
 }
 
+/* FIXME.  */
+
+recording::extended_asm *
+recording::block::end_with_extended_asm_goto (location *loc,
+					      const char *asm_template,
+					      int num_goto_blocks,
+					      block **goto_blocks,
+					      block *fallthrough_block)
+{
+  extended_asm *result
+    = new extended_asm_goto (this, loc, new_string (asm_template),
+			     num_goto_blocks, goto_blocks,
+			     fallthrough_block);
+  m_ctxt->record (result);
+  m_statements.safe_push (result);
+  m_has_been_terminated = true;
+  return result;
+}
+
 /* Override the default implementation of
    recording::memento::write_to_dump for blocks by writing
    an unindented block name as a label, followed by the indented
@@ -6358,6 +6390,246 @@ recording::switch_::write_reproducer (reproducer &r)
 	     cases_id);
 }
 
+/* class output_asm_operand : public asm_operand.  */
+
+recording::string *
+recording::output_asm_operand::make_debug_string ()
+{
+  gcc_unreachable (); // TODO
+}
+
+void
+recording::output_asm_operand::write_reproducer (reproducer &r)
+{
+  // TODO
+}
+
+/* class input_asm_operand : public asm_operand.  */
+
+recording::string *
+recording::input_asm_operand::make_debug_string ()
+{
+  gcc_unreachable (); // TODO
+}
+
+void
+recording::input_asm_operand::write_reproducer (reproducer &r)
+{
+  // TODO
+}
+
+/* The implementation of class gcc::jit::recording::extended_asm.  */
+
+/* FIXME.  */
+
+void
+recording::extended_asm::add_output_operand (const char *asm_symbolic_name,
+					     const char *constraint,
+					     lvalue *dest)
+{
+  output_asm_operand *op
+    = new output_asm_operand (get_context (),
+			      new_string (asm_symbolic_name),
+			      new_string (constraint),
+			      dest);
+  m_ctxt->record (op);
+  m_output_ops.safe_push (op);
+}
+
+/* FIXME.  */
+
+void
+recording::extended_asm::add_input_operand (const char *asm_symbolic_name,
+					    const char *constraint,
+					    rvalue *src)
+{
+  input_asm_operand *op
+    = new input_asm_operand (get_context (),
+			     new_string (asm_symbolic_name),
+			     new_string (constraint),
+			     src);
+  m_ctxt->record (op);
+  m_input_ops.safe_push (op);
+}
+
+/* FIXME.  */
+
+void
+recording::extended_asm::add_clobber (const char *victim)
+{
+  m_clobbers.safe_push (new_string (victim));
+}
+
+/* Implementation of pure virtual hook recording::memento::replay_into
+   for recording::extended_asm.  */
+
+void
+recording::extended_asm::replay_into (replayer *r)
+{
+  auto_vec<playback::asm_operand> playback_output_ops;
+  auto_vec<playback::asm_operand> playback_input_ops;
+  auto_vec<const char *> playback_clobbers;
+  auto_vec<playback::block *> playback_goto_blocks;
+
+  /* Populate outputs.  */
+  {
+    output_asm_operand *rec_asm_op;
+    unsigned i;
+    FOR_EACH_VEC_ELT (m_output_ops, i, rec_asm_op)
+      {
+	playback::asm_operand playback_asm_op
+	  (rec_asm_op->get_symbolic_name (),
+	   rec_asm_op->get_constraint (),
+	   rec_asm_op->get_lvalue ()->playback_lvalue ()->as_tree ());
+	playback_output_ops.safe_push (playback_asm_op);
+      }
+  }
+
+  /* Populate inputs.  */
+  {
+    input_asm_operand *rec_asm_op;
+    unsigned i;
+    FOR_EACH_VEC_ELT (m_input_ops, i, rec_asm_op)
+      {
+	playback::asm_operand playback_asm_op
+	  (rec_asm_op->get_symbolic_name (),
+	   rec_asm_op->get_constraint (),
+	   rec_asm_op->get_rvalue ()->playback_rvalue ()->as_tree ());
+	playback_input_ops.safe_push (playback_asm_op);
+      }
+  }
+
+  /* Populate clobbers.  */
+  {
+    string *rec_clobber;
+    unsigned i;
+    FOR_EACH_VEC_ELT (m_clobbers, i, rec_clobber)
+      playback_clobbers.safe_push (rec_clobber->c_str ());
+  }
+
+  /* Populate playback blocks if an "asm goto".  */
+  maybe_populate_playback_blocks (&playback_goto_blocks);
+
+  playback_block (get_block ())
+    ->add_extended_asm (playback_location (r),
+			m_asm_template->c_str (),
+			m_is_volatile, m_is_inline,
+			&playback_output_ops,
+			&playback_input_ops,
+			&playback_clobbers,
+			&playback_goto_blocks);
+}
+
+/* Implementation of recording::memento::make_debug_string for
+   an extended_asm "statement".  */
+
+recording::string *
+recording::extended_asm::make_debug_string ()
+{
+  // TODO: flags, operands, clobbers, gotos
+  return string::from_printf (m_ctxt,
+			      "asm (%s)",
+			      m_asm_template->c_str ());
+}
+
+/* Implementation of recording::memento::write_reproducer for
+   extended_asm.  */
+
+void
+recording::extended_asm::write_reproducer (reproducer &r)
+{
+  // TODO: flags, operands, clobbers, gotos
+#if 0
+  // TODO:
+  r.write ("  gcc_jit_block_add_extended_asm (%s, /*gcc_jit_block *block */\n"
+	   "                             %s, /* gcc_jit_location *loc */\n"
+	   "                             %s); /* const char *text */\n",
+	   r.get_identifier (get_block ()),
+	   r.get_identifier (get_loc ()),
+	   m_text->get_debug_string ());
+#endif
+}
+
+/* FIXME.  */
+
+void
+recording::extended_asm::
+maybe_populate_playback_blocks (auto_vec <playback::block *> *)
+{
+  /* Do nothing; not an "asm goto".  */
+}
+
+/* The implementation of class gcc::jit::recording::extended_asm_goto.  */
+
+/* FIXME.  */
+
+recording::extended_asm_goto::extended_asm_goto (block *b,
+						 location *loc,
+						 string *asm_template,
+						 int num_goto_blocks,
+						 block **goto_blocks,
+						 block *fallthrough_block)
+: extended_asm (b, loc, asm_template),
+  m_goto_blocks (num_goto_blocks),
+  m_fallthrough_block (fallthrough_block)
+{
+  for (int i = 0; i < num_goto_blocks; i++)
+    m_goto_blocks.quick_push (goto_blocks[i]);
+}
+
+/* FIXME.  */
+
+void
+recording::extended_asm_goto::replay_into (replayer *r)
+{
+  recording::extended_asm::replay_into (r);
+  if (m_fallthrough_block)
+    playback_block (get_block ())
+      ->add_jump (playback_location (r),
+		  m_fallthrough_block->playback_block ());
+}
+
+/* FIXME.  */
+
+vec <recording::block *>
+recording::extended_asm_goto::get_successor_blocks () const
+{
+  vec <block *> result;
+  result.create (m_goto_blocks.length () + 1);
+  if (m_fallthrough_block)
+    result.quick_push (m_fallthrough_block);
+  result.splice (m_goto_blocks);
+  return result;
+}
+
+/* FIXME.  */
+
+recording::string *
+recording::extended_asm_goto::make_debug_string ()
+{
+  gcc_unreachable (); // TODO
+}
+
+/* FIXME.  */
+
+void
+recording::extended_asm_goto::write_reproducer (reproducer &r)
+{
+  // TODO
+}
+
+/* FIXME.  */
+
+void
+recording::extended_asm_goto::
+maybe_populate_playback_blocks (auto_vec <playback::block *> *out)
+{
+  unsigned i;
+  block *b;
+  FOR_EACH_VEC_ELT (m_goto_blocks, i, b)
+    out->safe_push (b->playback_block ());
+}
+
 } // namespace gcc::jit
 
 } // namespace gcc
diff --git a/gcc/jit/jit-recording.h b/gcc/jit/jit-recording.h
index 726b9c4b837..e6b69e44ee2 100644
--- a/gcc/jit/jit-recording.h
+++ b/gcc/jit/jit-recording.h
@@ -1258,6 +1258,10 @@ public:
   add_comment (location *loc,
 	       const char *text);
 
+  extended_asm *
+  add_extended_asm (location *loc,
+		    const char *asm_template);
+
   statement *
   end_with_conditional (location *loc,
 			rvalue *boolval,
@@ -1279,6 +1283,13 @@ public:
 		   int num_cases,
 		   case_ **cases);
 
+  extended_asm *
+  end_with_extended_asm_goto (location *loc,
+			      const char *asm_template,
+			      int num_goto_blocks,
+			      block **goto_blocks,
+			      block *fallthrough_block);
+
   playback::block *
   playback_block () const
   {
@@ -2071,6 +2082,157 @@ private:
   auto_vec <case_ *> m_cases;
 };
 
+/* FIXME.  */
+
+// TODO: make this a mememto?  is lifetime tied to the context?
+
+class asm_operand : public memento
+{
+public:
+  asm_operand (context *ctxt,
+	       string *asm_symbolic_name,
+	       string *constraint)
+  : memento (ctxt),
+    m_asm_symbolic_name (asm_symbolic_name),
+    m_constraint (constraint)
+  {
+    /* m_asm_symbolic_name can be NULL.  */
+    gcc_assert (m_constraint);
+  }
+
+  const char *get_symbolic_name () const
+  {
+    if (m_asm_symbolic_name)
+      return m_asm_symbolic_name->c_str ();
+    else
+      return NULL;
+  }
+
+  const char *get_constraint () const
+  {
+    return m_constraint->c_str ();
+  }
+
+private:
+  string *m_asm_symbolic_name;
+  string *m_constraint;
+};
+
+class output_asm_operand : public asm_operand
+{
+public:
+  output_asm_operand (context *ctxt,
+		      string *asm_symbolic_name,
+		      string *constraint,
+		      lvalue *dest)
+  : asm_operand (ctxt, asm_symbolic_name, constraint),
+    m_dest (dest)
+  {}
+
+  lvalue *get_lvalue () const { return m_dest; }
+
+  void replay_into (replayer *) FINAL OVERRIDE {}
+
+private:
+  string * make_debug_string () FINAL OVERRIDE;
+  void write_reproducer (reproducer &r) FINAL OVERRIDE;
+
+private:
+  lvalue *m_dest;
+};
+
+class input_asm_operand : public asm_operand
+{
+public:
+  input_asm_operand (context *ctxt,
+		     string *asm_symbolic_name,
+		     string *constraint,
+		     rvalue *src)
+  : asm_operand (ctxt, asm_symbolic_name, constraint),
+    m_src (src)
+  {}
+
+  rvalue *get_rvalue () const { return m_src; }
+
+  void replay_into (replayer *) FINAL OVERRIDE {}
+
+private:
+  string * make_debug_string () FINAL OVERRIDE;
+  void write_reproducer (reproducer &r) FINAL OVERRIDE;
+
+private:
+  rvalue *m_src;
+};
+
+class extended_asm : public statement
+{
+public:
+  extended_asm (block *b,
+		location *loc,
+		string *asm_template)
+  : statement (b, loc),
+    m_asm_template (asm_template),
+    m_is_volatile (false),
+    m_is_inline (false)
+  {}
+
+  void set_volatile_flag (bool flag) { m_is_volatile = flag; }
+  void set_inline_flag (bool flag) { m_is_inline = flag; }
+
+  void add_output_operand (const char *asm_symbolic_name,
+			   const char *constraint,
+			   lvalue *dest);
+  void add_input_operand (const char *asm_symbolic_name,
+			  const char *constraint,
+			  rvalue *src);
+  void add_clobber (const char *victim);
+
+  void replay_into (replayer *r) OVERRIDE;
+
+  string *get_asm_template () const { return m_asm_template; }
+
+private:
+  string * make_debug_string () OVERRIDE;
+  void write_reproducer (reproducer &r) OVERRIDE;
+  virtual void maybe_populate_playback_blocks
+    (auto_vec <playback::block *> *out);
+
+private:
+  string *m_asm_template;
+  bool m_is_volatile;
+  bool m_is_inline;
+  auto_vec<output_asm_operand *> m_output_ops;
+  auto_vec<input_asm_operand *> m_input_ops;
+  auto_vec<string *> m_clobbers;
+};
+
+/* FIXME.  */
+
+class extended_asm_goto : public extended_asm
+{
+public:
+  extended_asm_goto (block *b,
+		     location *loc,
+		     string *asm_template,
+		     int num_goto_blocks,
+		     block **goto_blocks,
+		     block *fallthrough_block);
+
+  void replay_into (replayer *r) FINAL OVERRIDE;
+
+  vec <block *> get_successor_blocks () const FINAL OVERRIDE;
+
+private:
+  string * make_debug_string () FINAL OVERRIDE;
+  void write_reproducer (reproducer &r) FINAL OVERRIDE;
+  void maybe_populate_playback_blocks
+    (auto_vec <playback::block *> *out) FINAL OVERRIDE;
+
+private:
+  auto_vec <block *> m_goto_blocks;
+  block *m_fallthrough_block;
+};
+
 } // namespace gcc::jit::recording
 
 /* Create a recording::memento_of_new_rvalue_from_const instance and add
diff --git a/gcc/jit/libgccjit.c b/gcc/jit/libgccjit.c
index 3d04f6db3af..69938002cba 100644
--- a/gcc/jit/libgccjit.c
+++ b/gcc/jit/libgccjit.c
@@ -96,6 +96,11 @@ struct gcc_jit_timer : public timer
 {
 };
 
+struct gcc_jit_extended_asm : public gcc::jit::recording::extended_asm
+{
+};
+
+
 /**********************************************************************
  Error-handling.
 
@@ -3247,3 +3252,107 @@ gcc_jit_version_patchlevel (void)
   version_info vi;
   return vi.patchlevel;
 }
+
+/* FIXME.  */
+
+gcc_jit_extended_asm *
+gcc_jit_block_add_extended_asm (gcc_jit_block *block,
+				gcc_jit_location *loc,
+				const char *asm_template)
+{
+  RETURN_NULL_IF_NOT_VALID_BLOCK (block, loc);
+  gcc::jit::recording::context *ctxt = block->get_context ();
+  JIT_LOG_FUNC (ctxt->get_logger ());
+  /* LOC can be NULL.  */
+  RETURN_NULL_IF_FAIL (asm_template, ctxt, loc, "NULL asm_template");
+
+  return (gcc_jit_extended_asm *)block->add_extended_asm (loc, asm_template);
+}
+
+/* FIXME.  */
+
+extern gcc_jit_extended_asm *
+gcc_jit_block_end_with_extended_asm_goto (gcc_jit_block *block,
+					  gcc_jit_location *loc,
+					  const char *asm_template,
+					  int num_goto_blocks,
+					  gcc_jit_block **goto_blocks,
+					  gcc_jit_block *fallthrough_block)
+{
+  RETURN_NULL_IF_NOT_VALID_BLOCK (block, loc);
+  gcc::jit::recording::context *ctxt = block->get_context ();
+  JIT_LOG_FUNC (ctxt->get_logger ());
+  /* LOC can be NULL.  */
+  RETURN_NULL_IF_FAIL (asm_template, ctxt, loc, "NULL asm_template");
+  /* TODO: validate other inputs.  */
+  return (gcc_jit_extended_asm *)block->end_with_extended_asm_goto
+    (loc, asm_template,
+     num_goto_blocks, (gcc::jit::recording::block **)goto_blocks,
+     fallthrough_block);
+}
+
+/* FIXME.  */
+
+void
+gcc_jit_extended_asm_set_volatile_flag (gcc_jit_extended_asm *ext_asm,
+					int flag)
+{
+  RETURN_IF_FAIL (ext_asm, NULL, NULL, "NULL ext_asm");
+  ext_asm->set_volatile_flag (flag);
+}
+
+/* FIXME.  */
+
+void
+gcc_jit_extended_asm_set_inline_flag (gcc_jit_extended_asm *ext_asm,
+				      int flag)
+{
+  RETURN_IF_FAIL (ext_asm, NULL, NULL, "NULL ext_asm");
+  ext_asm->set_inline_flag (flag);
+}
+
+/* FIXME.  */
+
+void
+gcc_jit_extended_asm_add_output_operand (gcc_jit_extended_asm *ext_asm,
+					 const char *asm_symbolic_name,
+					 const char *constraint,
+					 gcc_jit_lvalue *dest)
+{
+  RETURN_IF_FAIL (ext_asm, NULL, NULL, "NULL ext_asm");
+  // TODO: check the other args!
+  // TODO: can't be called on an "asm goto"
+  ext_asm->add_output_operand (asm_symbolic_name, constraint, dest);
+}
+
+/* FIXME.  */
+
+extern void
+gcc_jit_extended_asm_add_input_operand (gcc_jit_extended_asm *ext_asm,
+					const char *asm_symbolic_name,
+					const char *constraint,
+					gcc_jit_rvalue *src)
+{
+  RETURN_IF_FAIL (ext_asm, NULL, NULL, "NULL ext_asm");
+  // TODO: check the other args!
+  ext_asm->add_input_operand (asm_symbolic_name, constraint, src);
+}
+
+/* FIXME.  */
+
+void
+gcc_jit_extended_asm_add_clobber (gcc_jit_extended_asm *ext_asm,
+				  const char *victim)
+{
+  RETURN_IF_FAIL (ext_asm, NULL, NULL, "NULL ext_asm");
+  RETURN_IF_FAIL (victim, NULL, NULL, "NULL victim");
+  ext_asm->add_clobber (victim);
+}
+
+#if 0
+/* FIXME.  */
+
+extern void
+gcc_jit_extended_asm_add_goto_label (gcc_jit_extended_asm *ext_asm,
+				     gcc_jit_block *block);
+#endif
diff --git a/gcc/jit/libgccjit.h b/gcc/jit/libgccjit.h
index 1c5a12e9c01..5c679688381 100644
--- a/gcc/jit/libgccjit.h
+++ b/gcc/jit/libgccjit.h
@@ -68,6 +68,7 @@ typedef struct gcc_jit_result gcc_jit_result;
 	     +- gcc_jit_lvalue
 		 +- gcc_jit_param
 	 +- gcc_jit_case
+	 +- gcc_jit_extended_asm
 */
 typedef struct gcc_jit_object gcc_jit_object;
 
@@ -138,6 +139,9 @@ typedef struct gcc_jit_param gcc_jit_param;
    destination block.  */
 typedef struct gcc_jit_case gcc_jit_case;
 
+/* FIXME.  */
+typedef struct gcc_jit_extended_asm gcc_jit_extended_asm;
+
 /* Acquire a JIT-compilation context.  */
 extern gcc_jit_context *
 gcc_jit_context_acquire (void);
@@ -1503,6 +1507,72 @@ gcc_jit_version_minor (void);
 extern int
 gcc_jit_version_patchlevel (void);
 
+/* Functions for adding inline assembler code, analogous to GCC's
+   "extended asm" syntax.
+
+   See https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html
+
+   FIXME: note about API entrypoints and version.
+   FIXME.  */
+
+#define LIBGCCJIT_HAVE_gcc_jit_block_add_extended_asm
+
+/* FIXME.  */
+
+extern gcc_jit_extended_asm *
+gcc_jit_block_add_extended_asm (gcc_jit_block *block,
+				gcc_jit_location *loc,
+				const char *asm_template);
+
+/* FIXME.  */
+
+extern gcc_jit_extended_asm *
+gcc_jit_block_end_with_extended_asm_goto (gcc_jit_block *block,
+					  gcc_jit_location *loc,
+					  const char *asm_template,
+					  int num_goto_blocks,
+					  gcc_jit_block **goto_blocks,
+					  gcc_jit_block *fallthrough_block);
+
+/* FIXME.  */
+
+extern void
+gcc_jit_extended_asm_set_volatile_flag (gcc_jit_extended_asm *ext_asm,
+					int flag);
+/* FIXME.  */
+
+extern void
+gcc_jit_extended_asm_set_inline_flag (gcc_jit_extended_asm *ext_asm,
+				      int flag);
+
+/* FIXME.  */
+
+extern void
+gcc_jit_extended_asm_add_output_operand (gcc_jit_extended_asm *ext_asm,
+					 const char *asm_symbolic_name,
+					 const char *constraint,
+					 gcc_jit_lvalue *dest);
+
+/* FIXME.  */
+
+extern void
+gcc_jit_extended_asm_add_input_operand (gcc_jit_extended_asm *ext_asm,
+					const char *asm_symbolic_name,
+					const char *constraint,
+					gcc_jit_rvalue *src);
+
+/* FIXME.  */
+
+extern void
+gcc_jit_extended_asm_add_clobber (gcc_jit_extended_asm *ext_asm,
+				  const char *victim);
+
+/* FIXME.  */
+
+extern void
+gcc_jit_extended_asm_add_goto_label (gcc_jit_extended_asm *ext_asm,
+				     gcc_jit_block *block);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
diff --git a/gcc/jit/libgccjit.map b/gcc/jit/libgccjit.map
index 6137dd4b4b0..1f04ede9ff6 100644
--- a/gcc/jit/libgccjit.map
+++ b/gcc/jit/libgccjit.map
@@ -186,4 +186,16 @@ LIBGCCJIT_ABI_13 {
     gcc_jit_version_major;
     gcc_jit_version_minor;
     gcc_jit_version_patchlevel;
-} LIBGCCJIT_ABI_12;
\ No newline at end of file
+} LIBGCCJIT_ABI_12;
+
+LIBGCCJIT_ABI_FIXME {
+  global:
+    gcc_jit_block_add_extended_asm;
+    gcc_jit_block_end_with_extended_asm_goto;
+    gcc_jit_extended_asm_set_volatile_flag;
+    gcc_jit_extended_asm_set_inline_flag;
+    gcc_jit_extended_asm_add_output_operand;
+    gcc_jit_extended_asm_add_input_operand;
+    gcc_jit_extended_asm_add_clobber;
+    gcc_jit_extended_asm_add_goto_label;
+} LIBGCCJIT_ABI_13;
diff --git a/gcc/testsuite/jit.dg/test-asm.c b/gcc/testsuite/jit.dg/test-asm.c
new file mode 100644
index 00000000000..363a46d4d8b
--- /dev/null
+++ b/gcc/testsuite/jit.dg/test-asm.c
@@ -0,0 +1,303 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libgccjit.h"
+
+#include "harness.h"
+
+/* DONE:
+   - example with outputs and inputs
+   - example with asm_symbolic_name
+   - example with clobber
+   - example with goto labels
+   - example with using a goto label by name, rather than by number
+
+   TODO:
+   - make the tests only run on x86
+   - example with volatile
+   - example with inline
+   - error-checking.  */
+
+/* FIXME.  */
+
+static gcc_jit_block *
+make_single_block_func (gcc_jit_context *ctxt, const char *func_name)
+{
+  gcc_jit_type *void_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_VOID);
+  gcc_jit_function *func
+    = gcc_jit_context_new_function (ctxt, NULL,
+				    GCC_JIT_FUNCTION_EXPORTED,
+				    void_type,
+				    func_name,
+				    0, NULL, 0);
+  return gcc_jit_function_new_block (func, "initial");
+}
+
+/* Create the equivalent of:
+
+     int src;
+     int dst;
+
+     void test_i386_basic_asm_1 (void)
+     {
+       asm ("mov %1, %0\n\t"
+	    "add $1, %0"
+	    : "=r" (dst)
+	    : "r" (src));
+     }
+
+     i.e. copy src to dst and add 1 to dst.  */
+
+static void
+create_test_i386_basic_asm_1 (gcc_jit_context *ctxt)
+{
+  gcc_jit_type *int_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
+  gcc_jit_lvalue *dst
+    = gcc_jit_context_new_global (ctxt, NULL,
+				  GCC_JIT_GLOBAL_EXPORTED,
+				  int_type, "dst");
+  gcc_jit_lvalue *src
+    = gcc_jit_context_new_global (ctxt, NULL,
+				  GCC_JIT_GLOBAL_EXPORTED,
+				  int_type, "src");
+
+  gcc_jit_block *block
+    = make_single_block_func (ctxt, "test_i386_basic_asm_1");
+
+  gcc_jit_extended_asm *ext_asm
+    = gcc_jit_block_add_extended_asm (block, NULL,
+				      "mov %1, %0\n\t"
+				      "add $1, %0");
+  gcc_jit_extended_asm_add_output_operand (ext_asm, NULL, "=r", dst);
+  gcc_jit_extended_asm_add_input_operand (ext_asm, NULL, "r",
+					  gcc_jit_lvalue_as_rvalue (src));
+
+  gcc_jit_block_end_with_void_return (block, NULL);
+}
+
+/* Create the equivalent of:
+     uint32_t Mask;
+     uint32_t Index;
+
+     void test_i386_basic_asm_2 (void)
+     {
+       asm ("bsfl %[aMask], %[aIndex]"
+	    : [aIndex] "=r" (Index)
+	    : [aMask] "r" (Mask)
+	    : "cc");
+     }
+
+   i.e. TODO.
+
+   This exercises symbolic names and clobbers.  */
+
+static void
+create_test_i386_basic_asm_2 (gcc_jit_context *ctxt)
+{
+  gcc_jit_type *uint32_type = gcc_jit_context_get_int_type (ctxt, 4, 0);
+  gcc_jit_lvalue *mask
+    = gcc_jit_context_new_global (ctxt, NULL,
+				  GCC_JIT_GLOBAL_EXPORTED,
+				  uint32_type, "Mask");
+  gcc_jit_lvalue *index
+    = gcc_jit_context_new_global (ctxt, NULL,
+				  GCC_JIT_GLOBAL_EXPORTED,
+				  uint32_type, "Index");
+
+  gcc_jit_block *block
+    = make_single_block_func (ctxt, "test_i386_basic_asm_2");
+
+  gcc_jit_extended_asm *ext_asm
+    = gcc_jit_block_add_extended_asm (block, NULL,
+				      "bsfl %[aMask], %[aIndex]");
+  gcc_jit_extended_asm_add_output_operand (ext_asm, "aIndex", "=r", index);
+  gcc_jit_extended_asm_add_input_operand (ext_asm, "aMask", "r",
+					  gcc_jit_lvalue_as_rvalue (mask));
+  gcc_jit_extended_asm_add_clobber (ext_asm, "cc");
+
+  gcc_jit_block_end_with_void_return (block, NULL);
+}
+
+/* Create the equivalent of:
+
+     int test_i386_basic_asm_3a (int p1, int p2)
+     {
+       asm goto ("btl %1, %0\n\t"
+		 "jc %l2"
+		 : // No outputs
+		 : "r" (p1), "r" (p2)
+		 : "cc"
+		 : carry);
+
+       return 0;
+
+      carry:
+       return 1;
+     }
+
+    or (the "_3b" variant) using a name rather than a number for the goto
+    label:
+       asm goto ("btl %1, %0\n\t"
+		 "jc %l[carry]"
+		 : // No outputs
+		 : "r" (p1), "r" (p2)
+		 : "cc"
+		 : carry);
+
+    This exercises control flow with an asm.  */
+
+static void
+create_test_i386_basic_asm_3 (gcc_jit_context *ctxt,
+			      const char *funcname,
+			      int use_name)
+{
+  gcc_jit_type *int_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT);
+  gcc_jit_param *p1 = gcc_jit_context_new_param (ctxt, NULL, int_type, "p1");
+  gcc_jit_param *p2 = gcc_jit_context_new_param (ctxt, NULL, int_type, "p2");
+  gcc_jit_param *params[2] = {p1, p2};
+  gcc_jit_function *func
+    = gcc_jit_context_new_function (ctxt, NULL,
+				    GCC_JIT_FUNCTION_EXPORTED,
+				    int_type,
+				    funcname,
+				    2, params, 0);
+  gcc_jit_block *b_start = gcc_jit_function_new_block (func, "start");
+  gcc_jit_block *b_fallthru = gcc_jit_function_new_block (func, "fallthru");
+  gcc_jit_block *b_carry = gcc_jit_function_new_block (func, "carry");
+
+  gcc_jit_rvalue *zero
+    = gcc_jit_context_new_rvalue_from_int (ctxt, int_type, 0);
+  gcc_jit_rvalue *one
+    = gcc_jit_context_new_rvalue_from_int (ctxt, int_type, 1);
+
+  const char *asm_template =
+    (use_name
+     ? /* Label referred to by name: "%l[carry]".  */
+       ("btl %1, %0\n\t"
+	"jc %l[carry]")
+     : /* Label referred to numerically: "%l2".  */
+       ("btl %1, %0\n\t"
+	"jc %l2"));
+
+  gcc_jit_extended_asm *ext_asm
+    = gcc_jit_block_end_with_extended_asm_goto (b_start, NULL,
+						asm_template,
+						1, &b_carry,
+						b_fallthru);
+  gcc_jit_extended_asm_add_input_operand (ext_asm, NULL, "r",
+					  gcc_jit_param_as_rvalue (p1));
+  gcc_jit_extended_asm_add_input_operand (ext_asm, NULL, "r",
+					  gcc_jit_param_as_rvalue (p2));
+  gcc_jit_extended_asm_add_clobber (ext_asm, "cc");
+
+  gcc_jit_block_end_with_return (b_fallthru, NULL, zero);
+  gcc_jit_block_end_with_return (b_carry, NULL, one);
+}
+
+void
+create_code (gcc_jit_context *ctxt, void *user_data)
+{
+  // TODO:
+  gcc_jit_context_set_bool_option (
+    ctxt,
+    GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE,
+    1);
+  gcc_jit_context_set_bool_option (
+    ctxt,
+    GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE,
+    1);
+  gcc_jit_context_set_bool_option (
+    ctxt,
+    GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE,
+    1);
+  gcc_jit_context_set_bool_option (
+    ctxt,
+    GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING,
+    1);
+
+  create_test_i386_basic_asm_1 (ctxt);
+  create_test_i386_basic_asm_2 (ctxt);
+  create_test_i386_basic_asm_3 (ctxt, "test_i386_basic_asm_3a", 0);
+  create_test_i386_basic_asm_3 (ctxt, "test_i386_basic_asm_3b", 1);
+}
+
+/* Verification.  */
+
+typedef void (*void_void_fn) (void);
+
+static void_void_fn
+get_test_fn (gcc_jit_result *result, const char *func_name)
+{
+  return (void_void_fn)gcc_jit_result_get_code (result, func_name);
+}
+
+static void
+verify_code_1 (gcc_jit_context *ctxt, gcc_jit_result *result)
+{
+  void_void_fn test_i386_basic_asm_1
+    = get_test_fn (result, "test_i386_basic_asm_1");
+  CHECK_NON_NULL (test_i386_basic_asm_1);
+
+  int *dst_ptr = (int *)gcc_jit_result_get_global (result, "dst");
+  CHECK_NON_NULL (dst_ptr);
+  int *src_ptr = (int *)gcc_jit_result_get_global (result, "src");
+  CHECK_NON_NULL (src_ptr);
+
+  *src_ptr = 42;
+  *dst_ptr = 0;
+  test_i386_basic_asm_1 ();
+  CHECK_VALUE (*src_ptr, 42);
+  CHECK_VALUE (*dst_ptr, 43);
+}
+
+static void
+verify_code_2 (gcc_jit_context *ctxt, gcc_jit_result *result)
+{
+  void_void_fn test_i386_basic_asm_2
+    = get_test_fn (result, "test_i386_basic_asm_2");
+  CHECK_NON_NULL (test_i386_basic_asm_2);
+
+  // TODO: verify behavior
+}
+
+static void
+verify_code_3 (gcc_jit_context *ctxt, gcc_jit_result *result,
+	       const char *funcname)
+{
+  typedef int (*test_i386_basic_asm_3_type) (int, int);
+
+  test_i386_basic_asm_3_type test_i386_basic_asm_3
+    = (test_i386_basic_asm_3_type) gcc_jit_result_get_code (result, funcname);
+  CHECK_NON_NULL (test_i386_basic_asm_3);
+
+  /* The fn should test bits, returning 0 or 1.  */
+  /* Bit 0.  */
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0000, 0), 0);
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0001, 0), 1);
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0002, 0), 0);
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0003, 0), 1);
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0004, 0), 0);
+  /* Bit 1.  */
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0000, 1), 0);
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0001, 1), 0);
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0002, 1), 1);
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0003, 1), 1);
+  CHECK_VALUE (test_i386_basic_asm_3 (0x0004, 1), 0);
+
+  for (int i = 0; i < 15; i++)
+    {
+      CHECK_VALUE (test_i386_basic_asm_3 (0x0000, i), 0);
+      CHECK_VALUE (test_i386_basic_asm_3 (0xffff, i), 1);
+    }
+}
+
+void
+verify_code (gcc_jit_context *ctxt, gcc_jit_result *result)
+{
+  CHECK_NON_NULL (result);
+  verify_code_1 (ctxt, result);
+  verify_code_2 (ctxt, result);
+  verify_code_3 (ctxt, result, "test_i386_basic_asm_3a");
+  verify_code_3 (ctxt, result, "test_i386_basic_asm_3b");
+}
-- 
2.26.2

